我想做一个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)
将从许多并发会话中调用该查询。我的性能测试表明它会在特定负载下持续抛出主键违规。
这个查询是否有高并发方法,可以保持性能,同时仍然避免插入已经存在的数据?
您可以使用LOCK来制作SERIALIZABLE,但这会降低并发性。为什么不首先尝试常见病症(“主要是插入或大部分选择”),然后安全处理“补救”行动?也就是说,“JFDI”模式......
大多数INSERT预期(球场70-80%+):
试着插入。如果失败,则表示已创建该行。无需担心并发性,因为TRY / CATCH会为您处理重复项。
BEGIN TRY
INSERT Table VALUES (@Value)
SELECT @id = SCOPEIDENTITY()
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
主要选择:
类似,但尝试先获取数据。无数据= 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 = SCOPEIDENTITY()
END
END TRY
BEGIN CATCH
IF ERROR_NUMBER() <> 2627
RAISERROR etc
ELSE
SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
END CATCH
第二个似乎重演,但它是高度并发的。锁会实现相同但却以并发为代价......
编辑:
为什么 不 使用MERGE ...
如果使用OUTPUT子句,它将仅返回更新的内容。因此,您需要一个虚拟UPDATE来为OUTPUT子句生成INSERTED表。如果你必须通过许多调用进行虚拟更新(如OP所暗示的那样),那就是大量的日志写入 只是 能够使用MERGE。
// CREATE TABLE Table (RowID INT NOT NULL IDENTITY(1,1), RowValue VARCHAR(50))
- 确保将RowValue和RowID上的非聚集唯一索引作为聚簇索引。
IF EXISTS (SELECT * FROM Table WHERE RowValue = @VALUE)
SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
ELSE BEGIN
INSERT Table VALUES (@Value)
SELECT @id = SCOPEIDENTITY()
END
一如既往,gbn的答案是正确的,最终将我带到了我需要的地方。但是,我发现了一个特殊的边缘案例,他的方法没有涵盖。那是一个 2601
标识a的错误 Unique Index Violation
。
为了弥补这一点,我修改了他的代码如下
...
declare @errornumber int = ERROR_NUMBER()
if @errornumber <> 2627 and @errornumber <> 2601
...
希望这有助于某人!