Каков правильный шаблон для атомного "UPSERT" (UPDATE, где существует, INSERT в противном случае) в SQL Server 2005?
Я вижу много кода на SO (например, см. Проверьте, существует ли строка, иначе вставьте) со следующим двухчастным шаблоном:
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 ...
где < условие > будет оценкой естественных ключей. Ни один из вышеперечисленных подходов, похоже, не справляется с concurrency. Если у меня не может быть двух строк с одним и тем же естественным ключом, кажется, что все перечисленные выше риски вставляют строки с одинаковыми естественными ключами в сценариях условий гонки.
Я использую следующий подход, но я удивлен, что не вижу его в ответах людей, поэтому мне интересно, что с ним не так:
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>
Обратите внимание, что упомянутое здесь условие гонки отличается от условий предыдущего кода. В более раннем коде проблема заключалась в phantom чтениях (строки вставляются между UPDATE/IF или между SELECT/INSERT другим сеансом). В приведенном выше коде условие гонки связано с DELETE. Возможно ли, чтобы соответствующая строка была удалена другим сеансом ПОСЛЕ того, как выполняется (WHERE NOT EXISTS), но до выполнения INSERT? Неясно, где WHERE NOT EXISTS помещает блокировку на что-либо в связи с UPDATE.
Является ли это атомом? Я не могу найти, где это будет документировано в документации SQL Server.
EDIT: Я понимаю, что это можно сделать с транзакциями, но я думаю, что мне нужно установить уровень транзакции SERIALIZABLE, чтобы избежать проблемы с чтением phantom? Неужели это слишком сложно для такой общей проблемы?