在 SQL Server 2005 中执行原子“UPSERT”(如果存在则更新,否则插入)的正确模式是什么?
我在 SO 上看到了很多代码(例如,请参阅Check if a row exists, else insert)具有以下两部分模式:
UPDATE ...
FROM ...
WHERE <condition>
-- race condition risk here
IF @@ROWCOUNT = 0
INSERT ...
或者
IF (SELECT COUNT(*) FROM ... WHERE <condition>) = 0
-- race condition risk here
INSERT ...
ELSE
UPDATE ...
其中 < 条件 > 将是对自然键的评估。上述方法似乎都不能很好地处理并发。如果我不能有两行具有相同的自然键,那么上述所有风险似乎都存在在竞争条件场景中插入具有相同自然键的行的风险。
我一直在使用以下方法,但我很惊讶在人们的反应中没有看到它,所以我想知道它有什么问题:
INSERT INTO <table>
SELECT <natural keys>, <other stuff...>
FROM <table>
WHERE NOT EXISTS
-- race condition risk here?
( SELECT 1 FROM <table> WHERE <natural keys> )
UPDATE ...
WHERE <natural keys>
请注意,这里提到的竞争条件与前面代码中的竞争条件不同。在较早的代码中,问题是幻读(行被另一个会话插入到 UPDATE/IF 之间或 SELECT/INSERT 之间)。在上面的代码中,竞争条件与 DELETE 有关。在(WHERE NOT EXISTS)执行之后但在 INSERT 执行之前,是否有可能由另一个会话删除匹配的行?目前尚不清楚 WHERE NOT EXISTS 在何处与 UPDATE 一起锁定任何内容。
这是原子的吗?我无法找到这将在 SQL Server 文档中记录的位置。
编辑: 我意识到这可以通过事务来完成,但我认为我需要将事务级别设置为 SERIALIZABLE 以避免幻读问题?对于这样一个常见的问题,这肯定是矫枉过正吗?