マットとアーウィンはどちらも正しいです。コメントに収まらない方法で彼らが言ったことをさらに拡張するために、私は別の答えを追加するだけです。彼らの答えはすべての人を満足させるものではないようであり、PostgreSQL開発者に相談するべきだという提案があったので、私もその1つです。
ここで重要な点は、SQL標準では、READ COMMITTED
トランザクション分離レベルで実行されているトランザクション内で、コミットされていないトランザクションの作業が表示されてはならないという制限があることです。ときにコミットされたトランザクションの作業が見えるようになり、実装依存です。あなたが指摘しているのは、2つの製品がそれを実装するために選択した方法の違いです。どちらの実装も標準の要件に違反していません。
PostgreSQL内で起こることの詳細は次のとおりです。
S1-1の実行(1行削除)
S1はまだロールバックする可能性があるため、古い行はそのまま残りますが、S1は行のロックを保持するため、行を変更しようとする他のセッションはS1がコミットまたはロールバックするかどうかを待機します。いずれかが読み彼らがそれをロックしようとしない限り、まだ、古い行を見ることができるテーブルのSELECT FOR UPDATE
かSELECT FOR SHARE
。
S2-1が実行されます(ただし、S1には書き込みロックがあるためブロックされます)
S2は、S1の結果を確認するために待機する必要があります。S1がコミットではなくロールバックする場合、S2は行を削除します。S1がロールバックする前に新しいバージョンを挿入した場合、新しいバージョンは他のトランザクションの観点からは存在せず、古いバージョンは他のトランザクションの観点から削除されないことに注意してください。
S1-2の実行(1行挿入)
この行は、古い行から独立しています。id = 1の行の更新があった場合、古いバージョンと新しいバージョンが関連付けられ、S2はブロックが解除されたときに行の更新されたバージョンを削除できます。新しい行がたまたま過去に存在したいくつかの行と同じ値を持つことは、その行の更新されたバージョンと同じにはなりません。
S1-3が実行され、書き込みロックが解除されます
したがって、S1の変更は保持されます。1行がなくなりました。1行追加されました。
S2-1が実行され、ロックを取得できるようになりました。ただし、0行が削除されたと報告されます。え?
内部で行われることは、行のあるバージョンから、同じ行の次のバージョンが更新された場合、その行へのポインターがあるということです。行が削除された場合、次のバージョンはありません。場合READ COMMITTED
トランザクションが書き込み競合上のブロックから目覚めさせ、それが最後までその更新鎖を、以下、行が削除されておらず、クエリの選択基準をまだ満たしている場合、処理されます。この行は削除されているため、S2のクエリは続行します。
S2は、テーブルのスキャン中に新しい行に到達する場合と到達しない場合があります。存在する場合は、S2のDELETE
ステートメントが開始された後に新しい行が作成されたことがわかるため、そこに表示される行セットの一部ではありません。
PostgreSQLが新しいスナップショットを使用してS2のDELETEステートメント全体を最初から再起動する場合、SQL Serverと同じように動作します。PostgreSQLコミュニティは、パフォーマンス上の理由からそうすることを選択していません。この単純なケースでは、パフォーマンスの違いに気付くことはありませDELETE
んが、ブロックされたときに1000万行が入っていた場合、間違いなく気付くでしょう。高速バージョンは依然として標準の要件に準拠しているため、PostgreSQLがパフォーマンスを選択した場合、トレードオフがあります。
S2-2が実行され、一意キー制約違反が報告されます
もちろん、行はすでに存在します。これは写真の最も驚くべき部分です。
ここには驚くべき動作がいくつかありますが、すべてがSQL標準に準拠しており、標準に従って「実装固有」であるものの範囲内です。他の実装の動作がすべての実装に存在すると想定している場合、確かに驚くかもしれませんが、PostgreSQLはREAD COMMITTED
分離レベルでのシリアル化の失敗を避けるために非常に努力し、それを達成するために他の製品とは異なる動作を許可します。
今、私は個人的にREAD COMMITTED
、どの製品の実装でもトランザクション分離レベルの大ファンではありません。これらはすべて、競合状態がトランザクションの観点から驚くべき動作を作成することを可能にします。誰かが1つの製品で許可されている奇妙な動作に慣れると、「正常」であり、別の製品で選択されたトレードオフが奇妙であると考える傾向があります。しかし、すべての製品は、実際にとして実装されていないモードに対して何らかのトレードオフを行う必要がありますSERIALIZABLE
。PostgreSQL開発者が線を引くことを選択したのは、READ COMMITTED
ブロッキングを最小限に抑えることです(読み取りは書き込みをブロックせず、書き込みは読み取りをブロックしません)。そして、シリアル化の失敗の可能性を最小限にします。
標準では、SERIALIZABLE
トランザクションをデフォルトにする必要がありますが、ほとんどの製品は、より緩やかなトランザクション分離レベルでパフォーマンスヒットを引き起こすため、これを行いません。一部の製品SERIALIZABLE
は、選択されたときに真にシリアル化可能なトランザクションさえ提供しません。最も顕著なのは、Oracleおよび9.1より前のPostgreSQLのバージョンです。しかし、真のSERIALIZABLE
トランザクションを使用することは、競合状態からの驚くべき影響を回避する唯一の方法であり、SERIALIZABLE
トランザクションは常に競合状態を回避するためにブロックするか、競合状態の発生を回避するために一部のトランザクションをロールバックする必要があります。SERIALIZABLE
トランザクションの最も一般的な実装は、ブロッキングとシリアル化の両方の障害(デッドロックの形で)が発生するStrict Two-Phase Locking(S2PL)です。
完全な開示:MITのダンポートと協力して、シリアライズ可能なスナップショット分離と呼ばれる新しい手法を使用して、PostgreSQLバージョン9.1に真にシリアライズ可能なトランザクションを追加しました。