这三个版本的 TSQL 片段有什么区别?

Faj*_*iya 5 sql-server hints upsert design-pattern

版本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;
Run Code Online (Sandbox Code Playgroud)

版本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;
Run Code Online (Sandbox Code Playgroud)

版本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;
Run Code Online (Sandbox Code Playgroud)

我对提示的位置感到困惑。如果部分和子查询中的表INSERT相同,那么在哪里编写提示还有区别吗?

Pau*_*ite 8

这些提示仅适用于它们所在的位置。源表和目标表相同并不重要。

需要这些提示来NOT EXISTS确保 (a) 该行保持锁定足够长的时间;(b) 如果测试范围内不存在行,则在事务持续时间内继续出现这种情况。在存在测试中暗示读取是实现这些目标的最可靠方法。

版本 1 是正确的“upsert”模式之一,用于预计插入会更常见的情况。

版本 2 缺乏有关表读取的必要提示,以便在并发情况下正确工作,同时最大限度地减少死锁。NOT EXISTS在插入发生之前,另一个会话有一小段机会在该范围内插入一行。

版本 3 不必要地重复插入目标上的提示,但它在其他方面是无害的。

请参阅Michael Swart 撰写的SQL Server UPSERT 模式和反模式。