SQL Server 2005 でアトミックな "UPSERT" (存在する場合は UPDATE、存在しない場合は INSERT) を実行するための正しいパターンは何ですか?
SO には、次の 2 つの部分からなるパターンを持つ多くのコードが表示されます (たとえば、「行が存在するかどうかを確認し、そうでない場合は挿入する」を参照)。
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 ...
ここで、<条件> は自然キーの評価になります。上記のアプローチはどれも、並行性をうまく処理していないようです。同じ自然キーを持つ 2 つの行を持つことができない場合、上記のすべてが競合状態のシナリオで同じ自然キーを持つ行を挿入するリスクがあるようです。
私は次のアプローチを使用していますが、人々の回答のどこにも見られないことに驚いているので、何が問題なのか疑問に思っています:
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 に設定する必要があると思いますか? 確かに、そのような一般的な問題にはやり過ぎですか?