只有在它不存在的情况下才插入一行

时间:2010-08-04 16:56:11

标签: sql sql-server tsql concurrency locking

我总是使用类似于以下的东西来实现它:

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)

虽然,也许我使用了错误的锁或使用过多的锁定等等。

我在stackoverflow.com上看到了其他问题,答案是建议“IF(SELECT COUNT(*)... INSERT”等等),但我总是在(可能是不正确的)假设单个SQL语句会是原子的。

有没有人有任何想法?

6 个答案:

答案 0 :(得分:56)

"JFDI"模式怎么样?

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

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

Read lesson 4

  

第4课:在调整索引之前开发upsert proc时,我首先相信If Exists(Select…)行会触发任何项目并禁止重复。纳达。在短时间内有数千个重复项,因为相同的项目将在相同的毫秒内达到upsert并且两个事务都会看到不存在并执行插入。经过大量测试后,解决方案是使用唯一索引,捕获错误,然后重试允许事务查看行并执行更新而不是插入。

答案 1 :(得分:23)

我添加了最初不存在的HOLDLOCK。如果没有此提示,请忽略该版本。

就我而言,这应该足够了:

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

另外,如果你真的想要更新一行(如果它存在)并插入(如果不存在),你可能会发现this question有用。

答案 2 :(得分:17)

您可以使用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)

答案 3 :(得分:1)

我不知道这是否是“官方”方式,但您可以尝试INSERT,如果失败则退回UPDATE

答案 4 :(得分:1)

首先,对我们的男人@gbn大肆宣传他对社区的贡献。甚至无法解释我多久发现自己遵循他的建议。

无论如何,足够的粉丝。

稍微补充一下他的回答,或许是#34;增强&#34;它。对于那些像我一样的人,左边的感觉与<> 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

答案 5 :(得分:-4)

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

     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