这篇经典的并发安全文章显然是为一次只更新一行而设计的。在我的例子中,我有一个表值输入,并且希望以并发安全的方式更新每一行。我知道这并不总是可行的,但我希望尽可能地接近这一点。MERGE
这似乎是一个自然的解决方案,但我并不信任它,而且它确实很容易出现 bug。Michael J. Swart 文章中剩下的两种方法是:
- 事务内部的锁提示(更新更常见)
CREATE PROCEDURE s_AccountDetails_Upsert ( @Email nvarchar(4000), @Etc nvarchar(max) )
AS
SET XACT_ABORT ON;
BEGIN TRAN
UPDATE TOP (1) dbo.AccountDetails WITH (UPDLOCK, SERIALIZABLE)
SET Etc = @Etc
WHERE Email = @Email;
IF (@@ROWCOUNT = 0)
BEGIN
INSERT dbo.AccountDetails ( Email, Etc )
VALUES ( @Email, @Etc );
END
COMMIT
- 事务内部的锁提示(插入更常见的)
CREATE PROCEDURE s_AccountDetails_Upsert ( @Email nvarchar(4000), @Etc nvarchar(max) )
AS
SET XACT_ABORT ON;
BEGIN TRAN
INSERT dbo.AccountDetails ( Email, Etc )
SELECT @Email, @Etc
WHERE NOT EXISTS (
SELECT *
FROM dbo.AccountDetails WITH (UPDLOCK, SERIALIZABLE)
WHERE Email = @Email
)
IF (@@ROWCOUNT = 0)
BEGIN
UPDATE TOP (1) dbo.AccountDetails
SET Etc = @Etc
WHERE Email = @Email;
END
COMMIT
我可以调整其中任何一个以使用表变量(例如,我怀疑IF (@@ROWCOUNT = 0)
需要完全删除表变量),但是使用表值输入是否明显表明我们应该选择第一个或第二个解决方案?如果不是,那么应该基于什么做出决定?
先执行更新,然后使用
where not exists
子句执行插入。无法测试,@@rowcount
因为某些行可能已被更新。如果您先进行插入,那么您也会更新刚刚插入的行。
问题
MERGE
通常体现在功能组合(尤其是相对较新的功能)上,这会导致数据更改计划极其复杂。这很遗憾,因为MERGE
毫无疑问,这是一种表达“upsert”之类的便捷方式。解决方法
您可以考虑以下事项:
INSTEAD OF
触发器。INSERT
UPDATE
MERGE
反对该观点。优点:
MERGE
目标简单,所以数据变更计划并不复杂。缺点:
OUTPUT
子句不适用于插入的引用。这种解决方法允许您编写一个方便的
MERGE
,但将生成的操作作为单独的简单插入和更新执行。MERGE
仍然需要采取通常的并发预防措施。我使用了一个视图,假设您不希望
INSTEAD OF
在基表上使用触发器。*
INSTEAD OF
触发器不使用行版本控制。请参阅我的文章“关于 INSTEAD OF 触发器的有趣之处”。演示
还有一些需要改进的地方(比如可以合并触发器)。以下代码优先考虑清晰度:
db<>小提琴
表格和示例行
看法
INSTEAD OF INSERT
查看触发器INSTEAD OF UPDATE
查看触发器表值
MERGE
结果
MERGE
计划整理