CTEが失われた更新を受け入れるのはなぜですか?


8

クレイグリンガーがコメントしたときの意味がわかりません。

挿入トランザクションがロールバックすると、このソリューションは更新が失われる可能性があります。UPDATEが行に影響を与えたことを確認するチェックはありません。

https://stackoverflow.com/a/8702291/14731。失われた更新がどのように発生するかを示すイベントのサンプルシーケンスを提供してください(例:スレッド1がX、スレッド2がY)。


1
複雑なトピックについて1年以上前に残したコメントについて質問してください...楽しい!今、私はそこにあった正確な問題が何であったかを思い出さなければなりません。今それを再検討します。
クレイグリンガー

回答:


14

おそらく、2つの別個のステートメントについて、前の回答にそのコメントを追加するつもりだったと思います。一年以上前だったので、もう完全にはわかりません。

wCTEベースのクエリは、想定されている問題を実際に解決するわけではありませんが、1年後にもう一度確認すると、wCTEバージョンの更新が失われる可能性はありません。

(これらのソリューションはすべて、トランザクションごとに1つの行だけを変更しようとする場合にのみ機能します。1つのトランザクションで複数の変更を行おうとすると、ロールバックで再試行ループが必要になるため、事態が複雑になります。最低でも各変更の間にセーブポイントを使用する必要があります。)

失われた更新の対象となる2文のバージョン。

2つの個別のステートメントを使用するバージョンは、アプリケーションがUPDATEステートメントとステートメントから影響を受ける行の数をチェックし、INSERT両方がゼロの場合に再試行しない限り、更新が失われる可能性があります。

2つのトランザクションをREAD COMMITTED分離するとどうなるか想像してみてください。

  • TX1は実行されますUPDATE(影響なし)
  • TX1はINSERT(行を挿入)を実行します
  • TX2が実行されますUPDATE(影響なし、TX1によって挿入された行はまだ表示されていません)
  • TX1 COMMITs。
  • TX2は、INSERTTX1によってコミットされた行を確認できる新しいスナップショットを取得する*を実行します。EXISTSTX2はTX1によって挿入された行を見ることができるため、この句はtrueを返します。

したがって、TX2は効果がありません。アプリが更新からの行数をチェックし、両方がゼロ行を報告する場合は挿入と再試行を行わない限り、トランザクションが影響を及ぼさなかったことがわからないため、楽に続行します。

影響を受ける行数を確認できる唯一の方法は、マルチステートメントではなく2つの個別のステートメントとして実行するか、プロシージャを使用することです。

SERIALIZABLE分離を使用できますが、シリアル化の失敗に対処するには再試行ループが必要です。

が別のクエリではなく行に影響するINSERTかどうかが条件であるため、wCTEバージョンは更新の消失の問題から保護しUPDATEます。

wCTEは固有の違反を排除しません

書き込み可能なCTEバージョンはまだ信頼できるアップサートではありません。

これを同時に実行する2つのトランザクションを考えます。

  • どちらもVALUES句を実行します。

  • これで、両方がそのUPDATE部分を実行します。UPDATEwhere句に一致する行がないため、どちらも更新から空の結果セットを返し、変更を加えません。

  • これで両方がINSERT部分を実行します。UPDATE両方のクエリでゼロの行が返されたため、どちらもINSERT行への試行を試みます。

1つは成功します。1つは固有の違反をスローして中止します。

アプリがクエリ(つまり、きちんと作成されたアプリ)からのエラー結果をチェックして再試行する限り、これはデータ損失の懸念の原因にはなりませんが、ソリューションは既存の2ステートメントバージョンよりも優れています。再試行ループの必要性がなくなるわけではありません。

wCTEが既存の2ステートメントバージョンよりも優れている点は、テーブルに対して個別のクエリを使用する代わりに、の出力を使用してUPDATEかどうかを決定することINSERTです。これは一部最適化ですが、更新が失われる2ステートメントバージョンの問題から一部を保護します。下記参照。

wCTEをSERIALIZABLE分離して実行できますが、固有の違反ではなく、シリアライゼーションエラーが発生します。再試行ループの必要性は変わりません。

wCTEは失われた更新に対して脆弱ではないようです

私のコメントは、この解決策は更新が失われる可能性があることを示唆しましたが、検討したところ、私は間違っていたのではないかと思います。

それは1年以上前で、正確な状況を思い出すことはできませんが、1つの挿入トランザクションが別の挿入トランザクションが挿入またはロールするのを待機できるようにするために、一意のインデックスにトランザクション可視性ルールからの部分的な例外があるという事実を見逃したと思います続行する前に戻ってください。

あるいはINSERT、wCTE内のがUPDATE、候補行がテーブルに存在するかどうかではなく、影響を受ける行があるかどうかに条件があるという事実を見落としたのかもしれません。

INSERT一意のインデックスで競合するsは、コミット/ロールバックを待機します

行を挿入して、クエリの1つのコピーを実行するとします。変更はまだコミットされていません。新しいタプルはヒープと一意のインデックスに存在しますが、分離レベルに関係なく、他のトランザクションからはまだ見えません。

これで、クエリの別のコピーが実行されます。最初のコピーがコミットされていないため、挿入された行はまだ表示されていないため、更新は何にも一致しません。クエリは引き続き挿入を試みます。これにより、別の進行中のトランザクションが同じキー挿入していることがわかり、そのトランザクションがコミットまたはロールバックするのを待機してブロックされます

最初のトランザクションがコミットすると、上記のように、2番目のトランザクションは一意の違反で失敗します。最初のトランザクションがロールバックすると、代わりに2番目のトランザクションが挿入を続行します。

行数INSERTに依存することで、UPDATE更新が失われるのを防ぎます

2つのステートメントの場合とは異なり、wCTEは更新の損失に対して脆弱ではないと思います。

UPDATEが機能しない場合、はINSERT常に実行さUPDATEれます。これは、外部テーブルの状態ではなく、が何かをしたかどうかに厳密に依存するためです。したがって、それでも固有の違反で失敗する可能性がありますが、影響を与えずに更新を完全に失うことはありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.