クレイグリンガーがコメントしたときの意味がわかりません。
挿入トランザクションがロールバックすると、このソリューションは更新が失われる可能性があります。 UPDATEが行に影響を与えることを強制するチェックはありません。
オン https://stackoverflow.com/a/8702291/14731 。失われた更新がどのように発生するかを示すイベントのサンプルシーケンス(スレッド1がX、スレッド2がYなど)を提供してください。
おそらく、2つの別個のステートメントについて、前の回答にそのコメントを追加するつもりだったと思います。一年以上前だったので、もう完全にはわかりません。
WCTEベースのクエリは本来想定されている問題を実際に解決するわけではありませんが、1年後にもう一度確認すると、wCTEバージョンの更新が失われる可能性はありません。
(これらのソリューションはすべて、トランザクションごとに1つの行だけを変更しようとする場合にのみ機能します。1つのトランザクションで複数の変更を行おうとすると、ロールバックでの再試行ループが必要になるため、混乱が生じます。最低でも変更ごとにセーブポイントを使用する必要があります。)
2つの個別のステートメントを使用するバージョン は、アプリケーションがUPDATE
ステートメントとINSERT
ステートメントから影響を受けた行の数をチェックし、両方の場合に再試行しない限り、更新が失われる可能性がありますゼロです。
READ COMMITTED
分離で2つのトランザクションがある場合に何が起こるか想像してみてください。
UPDATE
を実行します(影響なし)INSERT
を実行します(行を挿入します)UPDATE
を実行します(影響なし、TX1によって挿入された行はまだ表示されていません)COMMIT
s。INSERT
を実行します。これは、TX1によってコミットされた行を確認できる新しいスナップショットを取得します。 TX2がTX1によって挿入された行を見ることができるようになったため、EXISTS
句はtrueを返します。したがって、TX2は効果がありません。アプリが更新からの行数をチェックし、両方がゼロ行を報告した場合に挿入と再試行を行わない限り、トランザクションが影響を及ぼさなかったことをアプリは認識せず、楽に続行します。
影響を受ける行数を確認できる唯一の方法は、マルチステートメントではなく2つの個別のステートメントとして実行するか、プロシージャを使用することです。
SERIALIZABLE
分離を使用できますが、シリアル化の失敗に対処するには再試行ループが必要です。
INSERT
はUPDATE
が別のクエリではなく行に影響するかどうかに条件があるため、wCTEバージョンは失われた更新の問題から保護します。
書き込み可能なCTEバージョン はまだ信頼できるアップサートではありません。
これを同時に実行する2つのトランザクションを考えます。
どちらもVALUES句を実行します。
これで、どちらもUPDATE
部分を実行します。 UPDATE
s where句に一致する行がないため、どちらも更新から空の結果セットを返し、変更を加えません。
今度は両方ともINSERT
部分を実行します。 UPDATE
は両方のクエリでゼロ行を返したため、どちらも行をINSERT
しようとしました。
1つは成功します。 1つは固有の違反をスローして中止します。
アプリがクエリ(つまり、きちんと作成されたアプリ)からのエラー結果をチェックして再試行する限り、これはデータ損失の懸念の原因にはなりませんが、既存の2つのステートメントのバージョンよりも優れたソリューションになります。再試行ループの必要性がなくなるわけではありません。
WCTEが既存の2ステートメントバージョンよりも優れている点は、テーブルに対して個別のクエリを使用する代わりに、UPDATE
の出力を使用してINSERT
を決定することです。これは一部最適化ですが、更新が失われる2ステートメントバージョンの問題から一部を保護します。下記参照。
WCTEをSERIALIZABLE
分離で実行できますが、一意の違反ではなく、シリアル化の失敗が発生します。再試行ループの必要性は変わりません。
私のコメントは、この解決策は更新が失われる可能性があることを示唆していましたが、検討したところ、私は間違っていたのではないかと思います。
1年以上前であり、正確な状況を思い出すことはできませんが、ある挿入トランザクションが別の挿入トランザクションまたは挿入ロールを待機できるようにするために、一意のインデックスにトランザクション可視性ルールからの部分的な例外があるという事実を見逃したと思います続行する前に戻ってください。
または、wCTEのINSERT
が、候補行がテーブルに存在するかどうかではなく、UPDATE
が行に影響を与えるかどうかに依存しているという事実を見落としたかもしれません。
一意のインデックスで競合するINSERT
sがコミット/ロールバックを待機しています
行を挿入して、クエリの1つのコピーを実行するとします。変更はまだコミットされていません。新しいタプルはヒープと一意のインデックスに存在しますが、分離レベルに関係なく、まだ他のトランザクションからは見えません。
これで、クエリの別のコピーが実行されます。最初のコピーがコミットされていないため、挿入された行はまだ表示されていないため、更新は何にも一致しません。クエリは引き続き挿入を試みます。これにより、別の進行中のトランザクションが同じキーを挿入していることがわかり、そのトランザクションがコミットまたはロールバックするのを待機してブロックされます。
最初のトランザクションがコミットすると、上記のように、2番目のトランザクションは一意の違反で失敗します。最初のトランザクションがロールバックすると、2番目のトランザクションは代わりに挿入を続行します。
INSERT
がUPDATE
行数に依存しているため、更新が失われるのを防ぎます
2つのステートメントの場合とは異なり、wCTEは更新の消失に対して脆弱ではないと思います。
UPDATE
が機能しない場合、INSERT
は常に実行されます。これは、UPDATE
が外部テーブルの状態ではなく、何かをしたかどうかに厳密に依存するためです。そのため、固有の違反で失敗する可能性がありますが、影響を与えずに更新を完全に失うことはありません。