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