select/插入一个Upsert的版本:是否有高并发性的devise模式?
我想做一个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)
查询将从许多并发会话中调用。 我的性能testing表明,它将持续抛出特定负载下的主键违规。
有没有这种查询的高并发性方法,将允许它保持性能,同时仍然避免插入已经存在的数据?
您可以使用LOCKs来使事情成为SERIALIZABLE,但是这会降低并发性。 为什么不先尝试一般条件(“大部分插入或大部分select”),然后安全处理“补救”操作? 就是说,“JFDI”模式…
大部分INSERT预计(球场70-80%+):
试着插入。 如果失败,该行已经被创build。 无需担心并发性,因为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
主要select:
类似的,但是先尝试获取数据。 没有数据=插入需要。 同样,如果两个并发调用尝试插入,因为他们都发现行缺lessTRY / 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
错误,它标识了Unique Index Violation
。
为了弥补这一点,我修改了他的代码如下
... declare @errornumber int = ERROR_NUMBER() if @errornumber <> 2627 and @errornumber <> 2601 ...
希望这可以帮助别人!