选择/插入Upsert的版本:是否有高并发的设计模式?

8kb*_*8kb 11 t-sql sql-server concurrency design-patterns sql-server-2008

我想做一个UPSERT的SELECT/INSERT版本.以下是现有代码的模板:

// CREATE TABLE Table (RowID INT NOT NULL IDENTITY(1,1), RowValue VARCHAR(50))

IF NOT EXISTS (SELECT * FROM Table WHERE RowValue = @VALUE)
BEGIN
   INSERT Table VALUES (@Value)
   SELECT @id = SCOPEIDENTITY()
END
ELSE
   SELECT @id = RowID FROM Table WHERE RowValue = @VALUE)
Run Code Online (Sandbox Code Playgroud)

将从许多并发会话中调用该查询.我的性能测试表明它会在特定负载下持续抛出主键违规.

是否有一种高并发方法可以使此查询保持性能,同时仍然避免插入已存在的数据?

gbn*_*gbn 15

您可以使用LOCK来生成SERIALIZABLE,但这会降低并发性.为什么不首先尝试共同条件("主要是插入或大部分选择"),然后安全处理"补救"行动?也就是说,"JFDI"模式......

大多数INSERT预期(球场70-80%+):

试着插入.如果失败,则表示已创建该行.无需担心并发性,因为TRY/CATCH会为您处理重复项.

BEGIN TRY
   INSERT Table VALUES (@Value)
   SELECT @id = SCOPE_IDENTITY()
END TRY
BEGIN CATCH
    IF ERROR_NUMBER() <> 2627
      RAISERROR etc
    ELSE -- only error was a dupe insert so must already have a row to select
      SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
END CATCH
Run Code Online (Sandbox Code Playgroud)

主要选择:

类似,但尝试先获取数据.无数据= INSERT需要.同样,如果2个并发调用尝试INSERT,因为它们都发现该行缺少TRY/CATCH句柄.

BEGIN TRY
   SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
   IF @@ROWCOUNT = 0
   BEGIN
       INSERT Table VALUES (@Value)
       SELECT @id = SCOPE_IDENTITY()
   END
END TRY
BEGIN CATCH
    IF ERROR_NUMBER() <> 2627
      RAISERROR etc
    ELSE
      SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
END CATCH
Run Code Online (Sandbox Code Playgroud)

第二个似乎重演,但它是高度并发的.锁定会实现相同但却以并发为代价......

编辑:

为什么使用MERGE ......

如果使用OUTPUT子句,它将仅返回更新的内容.因此,您需要一个虚拟UPDATE来为OUTPUT子句生成INSERTED表.如果你有做多的呼叫(如所暗示的OP)虚拟更新这是一个很大的日志写入刚刚才能够使用合并.