データベースおよびトランザクション処理(トランザクション管理)において、スナップショット分離とは、トランザクションで行われたすべての読み取りでデータベースの一貫したスナップショットが参照されること (実際には開始時に存在していた最後のコミットされた値が読み取られる) が保証され、そのスナップショット以降に行われた同時更新と競合する更新がない場合にのみ、トランザクション自体が正常にコミットされます。
スナップショット分離は、 InterBase、Firebird、Oracle、MySQL、[ 1 ] PostgreSQL、SQL Anywhere、MongoDB [ 2 ]、Microsoft SQL Server(2005以降)など、いくつかの主要なデータベース管理システムで採用されています。これが採用されている主な理由は、直列化可能性よりも優れたパフォーマンスを実現しながら、直列化可能性では回避できる並行性の異常のほとんど(すべてではない)を回避できるためです。実際には、スナップショット分離は、各データ項目(バージョン)の世代値が維持される多版型同時実行制御(MVCC)内に実装されています。MVCCは、オブジェクトが書き込まれるたびにデータベースオブジェクトの新しいバージョンを生成し、トランザクションによる(各オブジェクトの)いくつかの最新の関連バージョンの読み取り操作を許可することにより、並行性とパフォーマンスを向上させる一般的な方法です。スナップショット分離は、 SQL標準が禁止している「異常」をまったく示さないにもかかわらず、シリアル化できない(ANSIで定義された異常のない分離レベル)ため、 ANSI SQL -92標準の分離レベルの定義を批判するために使用されてきました[3]。
シリアル化可能性とは異なりますが、スナップショット分離はOracle では シリアル化可能と呼ばれることもあります。
スナップショット分離下で実行されるトランザクションは、トランザクション開始時に取得されたデータベースの個人用スナップショットに対して操作を行っているように見えます。トランザクションの終了時にコミットが正常に実行されるのは、トランザクションによって更新された値がスナップショット取得以降に外部から変更されていない場合のみです。このような書き込み-書き込み競合は、トランザクションを中止させます。
書き込みスキュー異常とは、2つのトランザクション(T1とT2)が重複するデータセット(例えば、値V1とV2)を同時に読み取り、同時に別々の更新(例えば、T1がV1を更新し、T2がV2を更新)を行い、最終的に同時にコミットする現象です。どちらのトランザクションも、他方の更新内容を確認できません。システムがシリアル化可能であれば、T1またはT2のいずれかが「最初に」発生し、他方から参照可能である必要があるため、このような異常は発生しません。一方、スナップショット分離では書き込みスキュー異常が許容されます。
具体的な例として、フィルという人物が保有する2つの残高V1とV2を考えてみましょう。銀行は、両方の残高の合計がマイナスにならない限り(つまり、V1 + V2 ≥ 0)、どちらか一方が赤字になることを許容します。現在、両方の残高は100ドルです。フィルは2つの取引を同時に開始し、T1はV1から200ドルを引き出し、T2はV2から200ドルを引き出します。
データベースがシリアル化可能なトランザクションを保証している場合、T1 をコーディングする最も簡単な方法は、V1 から $200 を差し引き、V1 + V2 ≥ 0 がまだ維持されていることを確認し、維持されていない場合は中止することです。T2 も同様に V2 から $200 を差し引き、V1 + V2 ≥ 0 を確認します。トランザクションはシリアル化する必要があるため、T1 が最初に発生して V1 = -$100、V2 = $100 となり、T2 が成功しないようにするか (V1 + (V2 - $200) が -$200 になるため)、T2 が最初に発生して同様に T1 がコミットしないようにするかのいずれかになります。
しかし、データベースがスナップショット分離(MVCC)状態にある場合、T1とT2はデータベースのプライベートスナップショットに対して操作を行います。つまり、それぞれがアカウントから200ドルを差し引き、スナップショット取得時に保持されていたもう一方のアカウントの値を使用して、新しい合計が0であることを確認します。どちらの更新も競合しないため、両方のコミットは正常に実行され、V1 = V2 = -100ドル、V1 + V2 = -200ドルとなります。
多版型同時実行制御(MVCC)を使用して構築された一部のシステムでは、スナップショット分離のみをサポートする場合があります。これにより、同時操作を気にすることなくトランザクションを続行でき、さらに重要な点として、トランザクションが最終的にコミットされる際にすべての読み取り操作を再検証する必要がなくなります。これは、MVCCが最新の履歴整合性状態を連続的に維持するため便利です。トランザクション中に保存する必要がある情報は、行われた更新のリストのみであり、コミット前に競合を比較的簡単にスキャンできます。ただし、MVCCシステム(MarkLogicなど)では、MVCCに加えてロックを使用して書き込みをシリアル化することで、パフォーマンス向上の一部を実現しつつ、より強力な「シリアル化可能性」レベルの分離をサポートします。
書き込みスキューの異常から生じる潜在的な不整合の問題は、直列化可能性を強制するためにトランザクションに(そうでなければ不必要な)更新を追加することで修正できます。[ 4 ] [ 5 ] [ 6 ] [ 7 ]
上記の例では、隠れた制約を明示的に定義し、各人物を合計残高にマッピングする新しいテーブルを追加することで、競合を具体化できます。フィルは合計残高が200ドルの状態で開始し、各トランザクションはそこから200ドルを差し引こうとするため、書き込み-書き込み競合が発生し、2つのトランザクションが同時に実行できなくなります。しかし、このアプローチは正規形に違反します。
あるいは、トランザクションの読み取りの1つを書き込みに昇格させることもできます。例えば、T2はV1 = V1と設定することで、T1との書き込み競合を人為的に作り出し、2つのトランザクションが同時に成功しないようにすることができます。ただし、この解決策は常に実行可能であるとは限りません。
したがって、一般的にスナップショット分離は、重要な制約を維持するという問題の一部をユーザーに負わせることになりますが、ユーザーは潜在的な落とし穴や解決策を理解していない可能性があります。この移行の利点は、パフォーマンスが向上することです。
スナップショット分離は、Oracle [ 8 ] [ 9 ] [ 10 ]およびPostgreSQL 9.1より前のバージョン[ 11 ] [ 12 ] [ 13 ]では「シリアライズ可能」モードと呼ばれており、「真のシリアライズ可能性」モードと混同される可能性があります。この決定には賛否両論がありますが、データベースシステムロジックにおける望ましくない異常な動作を回避するために、ユーザーはこの違いを認識する必要があることは明らかです。
スナップショット分離は、多版同時実行制御データベースの研究から生まれました。多版同時実行制御データベースでは、データベースの複数のバージョンが同時に維持され、読み取り側が書き込み側と衝突することなく実行できます。このようなシステムでは、このような分離レベルの自然な定義と実装が可能です。 [ 3 ]後にBorlandが所有するInterBaseは、バージョン4で完全な直列化可能性ではなく、SI(シリアル化)を提供していたことが認められており、[ 3 ] 1985年の最初のリリース以来、書き込みスキューの異常を許容していた可能性があります。 [ 14 ]
残念ながら、ANSI SQL-92標準はロックベースのデータベースを念頭に置いて作成されたため、MVCCシステムへの適用においては曖昧な部分が多くあります。Berensonらは1995年にSQL標準を批判する論文[ 3 ]を執筆し、ANSI SQL-92標準で規定されている標準的な異常を示さないものの、シリアル化可能なトランザクションと比較すると異常な動作を示す分離レベルの例としてスナップショット分離を挙げました。
2008年、Cahillらは、同時実行トランザクションの「危険な」トリプレットを検出して中止することで、書き込みスキュー異常を防止できることを示しました。[ 15 ]この直列化可能性の実装は、多版型同時実行制御データベース に適しており、PostgreSQL 9.1で採用されています。[ 12 ] [ 13 ] [ 16 ] では、直列化可能スナップショット分離(SSI)と呼ばれています。これを一貫して使用することで、上記の回避策は不要になります。スナップショット分離の欠点は、中止されたトランザクションが増加することです。これは、ワークロードに応じて、上記の回避策を適用したスナップショット分離よりもパフォーマンスが向上する場合もあれば、低下する場合もあります。