2010-08-04 71 views
62

我一直用类似下面的东西来实现它:只有插入行,如果它尚不存在

INSERT INTO TheTable 
SELECT 
    @primaryKey, 
    @value1, 
    @value2 
WHERE 
    NOT EXISTS 
    (SELECT 
     NULL 
    FROM 
     TheTable 
    WHERE 
     PrimaryKey = @primaryKey) 

...但一旦负载下,发生了主键冲突。这是根本插入这张表的唯一声明。那么这是否意味着上述说法不是原子的?

问题是,这几乎不可能随意重新创建。

也许我可以将其更改为类似以下内容:

INSERT INTO TheTable 
WITH 
    (HOLDLOCK, 
    UPDLOCK, 
    ROWLOCK) 
SELECT 
    @primaryKey, 
    @value1, 
    @value2 
WHERE 
    NOT EXISTS 
    (SELECT 
     NULL 
    FROM 
     TheTable 
    WITH 
     (HOLDLOCK, 
     UPDLOCK, 
     ROWLOCK) 
    WHERE 
     PrimaryKey = @primaryKey) 

虽然,也许我用错了锁或使用太多的锁什么的。我只是在一个单一的SQL语句(也许是不正确的)的假设下,如果我的答案是“IF(SELECT COUNT(*)... INSERT”将原子。

没有人有任何想法?

+3

您是否尝试过使用没有'WHEN MATCHED'子句的合并? – 2010-08-04 16:59:18

+3

你在什么版本的SQL Server? – 2010-08-04 16:59:21

+0

这取决于客户端。包括2000和2008 R2之间的任何内容。虽然我们可能在声明最初编写时已经有7次了! – Adam 2010-08-04 17:20:00

回答

51

"JFDI"模式呢?

BEGIN TRY 
    INSERT etc 
END TRY 
BEGIN CATCH 
    IF ERROR_NUMBER() <> 2627 
     RAISERROR etc 
END CATCH 

说真的,这是最快和最没有锁的并发,特别是在大容量时。 如果UPDLOCK升级并且整个表被锁定,该怎么办?

Read lesson 4

第4课:在制定与调整前的索引的更新插入PROC,我第一次相信,​​线将火任何项目,将禁止重复。纳达。在短时间内有成千上万的副本,因为相同的项目会在同一毫秒内触发upsert,并且两个事务都会看到不存在并执行插入操作。经过多次测试,解决方案是使用唯一索引,捕获错误,然后重试允许事务查看行并执行更新,而不是插入。

+0

谢谢 - 好的,我同意这可能是我最终会用到的,并且是实际问题的答案。 – Adam 2010-08-04 19:58:02

+1

我知道依靠这样的错误是不好的,但是我想知道如果只用一个简单的'INSERT'(不带'EXISTS')来执行这个操作会更好(即不管插入什么,只需忽略错误2627)。 – Adam 2010-08-04 20:02:40

+0

这取决于您是否大多数插入不存在的值或大部分值* *存在的值。在后一种情况下,我认为由于大量的例外情况被提出并被忽略,表现会更差。 – GSerg 2010-08-04 21:11:38

1

我不知道这是“官方”的方式,但你可以尝试的INSERT,并回落到UPDATE如果失败。

21

我加了HOLDLOCK,原本不存在,请忽略版本没有这个提示。

就我而言,这已经足够了:

INSERT INTO TheTable 
SELECT 
    @primaryKey, 
    @value1, 
    @value2 
WHERE 
    NOT EXISTS 
    (SELECT 0 
    FROM TheTable WITH (UPDLOCK, HOLDLOCK) 
    WHERE PrimaryKey = @primaryKey) 

另外,如果你真的想更新行,如果它存在,然后插入如果没有,你可能会发现this question有用。

+1

当行不存在时,你在锁定什么? – 2010-08-04 17:04:04

+2

索引中的相关范围(本例中为主键)。 – GSerg 2010-08-04 17:05:29

+0

@GSerg同意。 select语句的悲观/乐观锁定需要一个指令。 – DaveWilliamson 2010-08-04 17:06:07

-3

我在过去使用不同的方法完成了类似的操作。首先,我声明一个变量来保存主键。然后,我使用select语句的输出填充该变量,该语句用这些值查找记录。然后我做和IF声明。如果主键为空,则插入,否则返回一些错误代码。

 DECLARE @existing varchar(10) 
    SET @existing = (SELECT primaryKey FROM TABLE WHERE param1field = @param1 AND param2field = @param2) 

    IF @existing is not null 
    BEGIN 
    INSERT INTO Table(param1Field, param2Field) VALUES(param1, param2) 
    END 
    ELSE 
    Return 0 
END 
+0

为什么不只是做: IF NOT EXISTS(SELECT * FROM表,其中param1field = @参数1和param2field = @参数2) BEGIN INSERT INTO表(param1Field,param2Field)VALUES(参数1,参数2) END – 2010-08-04 17:29:21

+0

是的,但是看起来它对并发性问题是开放的(例如,如果在select和insert之间的另一个连接上发生了什么?) – Adam 2010-08-04 17:31:36

+2

@Adam Marc的代码对于避免锁定问题并不是更好。处理并发问题的唯一两种方法是使用WITH(UPDLOCK,HOLDLOCK)进行锁定或处理插入错误并将其转换为更新。 – ErikE 2010-10-02 01:06:07

15

你可以用MERGE:

MERGE INTO Target 
USING (VALUES (@primaryKey, @value1, @value2)) Source (key, value1, value2) 
ON Target.key = Source.key 
WHEN MATCHED THEN 
    UPDATE SET value1 = Source.value1, value2 = Source.value2 
WHEN NOT MATCHED BY TARGET THEN 
    INSERT (Name, ReasonType) VALUES (@primaryKey, @value1, @value2) 
+0

在这种情况下,您可以删除'WHEN MATCHED THEN',因为Adam只需要在缺失时插入,而不是插入。 – Iain 2011-04-21 16:22:19

+3

对不起,但是没有在合并语句中添加锁定提示,您将会得到OP关心的确切问题。 – EBarr 2011-06-11 21:53:49

+7

查看[这篇文章](http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx)更多关于@ EBarr的点 – 2011-12-29 16:03:45

0

首先,巨大的喊出我们的人@gbn他对社会的贡献。甚至无法解释我多久听到他的建议后才发现自己。

无论如何,足够fanboy ing。

稍微增加他的回答,也许“增强”它。对于像我这样的人,在<> 2627的情况下,不知道该怎么办(没有空的CATCH不是一个选项)。我从technet发现了这个小块。

BEGIN TRY 
     INSERT etc 
    END TRY 
    BEGIN CATCH 
     IF ERROR_NUMBER() <> 2627 
      BEGIN 
       DECLARE @ErrorMessage NVARCHAR(4000); 
       DECLARE @ErrorSeverity INT; 
       DECLARE @ErrorState INT; 

       SELECT @ErrorMessage = ERROR_MESSAGE(), 
       @ErrorSeverity = ERROR_SEVERITY(), 
       @ErrorState = ERROR_STATE(); 

        RAISERROR (
         @ErrorMessage, 
         @ErrorSeverity, 
         @ErrorState 
        ); 
      END 
    END CATCH 
相关问题