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)虚拟更新这是一个很大的日志写入刚刚才能够使用合并.