版本 1
DECLARE @key INTEGER = 33, @val INTEGER = 44;
BEGIN TRANSACTION;
INSERT dbo.t([key], val)
SELECT @key, @val
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.t WITH (UPDLOCK, SERIALIZABLE)
WHERE [key] = @key
);
IF @@ROWCOUNT = 0
BEGIN
UPDATE dbo.t SET val = @val WHERE [key] = @key;
END
COMMIT TRANSACTION;
版本 2
DECLARE @key INTEGER = 33, @val INTEGER = 44;
BEGIN TRANSACTION;
INSERT dbo.t WITH (UPDLOCK, SERIALIZABLE) ([key], val)
SELECT @key, @val
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.t
WHERE [key] = @key
);
IF @@ROWCOUNT = 0
BEGIN
UPDATE dbo.t SET val = @val WHERE [key] = @key;
END
COMMIT TRANSACTION;
版本 3
DECLARE @key INTEGER = 33, @val INTEGER = 44;
BEGIN TRANSACTION;
INSERT dbo.t WITH (UPDLOCK, SERIALIZABLE) ([key], val)
SELECT @key, @val
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.t WITH (UPDLOCK, SERIALIZABLE)
WHERE [key] = @key
);
IF @@ROWCOUNT = 0
BEGIN
UPDATE dbo.t SET val = @val WHERE [key] = @key;
END
COMMIT TRANSACTION;
我对提示的位置感到困惑。如果INSERT
部分中的表和子查询相同,那么在哪里写提示仍然有区别吗?
提示仅适用于它们所在的位置。源表和目标表是否相同并不重要。
需要提示
NOT EXISTS
以确保 (a) 行保持锁定足够长的时间;(b) 如果测试范围内不存在行,则在事务期间继续存在这种情况。在存在测试中提示读取是实现这些目标的最可靠方法。版本 1 是正确的“upsert”模式之一,用于预期插入更常见的地方。
版本 2 缺少表读取的必要提示,无法在并发下正确工作,同时最大限度地减少死锁。
NOT EXISTS
在插入发生之前,另一个会话在范围内插入一行的机会很小。版本 3 不必要地复制了插入目标上的提示,但它在其他方面是无害的。
请参阅Michael Swart 的SQL Server UPSERT 模式和反模式。