# スナップショット分離とMVCC ## 定義 スナップショット分離(snapshot isolation)とは、各トランザクションがトランザクション開始時点でコミット済みだった一貫したスナップショットからのみデータを読む分離レベルである。他のトランザクションがその後データを変更しても、そのトランザクションは開始時点の旧データを読み続ける。長時間実行する読み取り専用クエリ(バックアップ・分析クエリ・整合性チェック)に有用で、PostgreSQL・MySQL(InnoDB)・Oracle・SQL Server など多くの DB が変種を提供する。(Source: [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]] "Snapshot Isolation and Repeatable Read") スナップショット分離は通常、**多版型並行性制御(multiversion concurrency control、MVCC)**で実装される。read committed のダーティリード防止(コミット済み値と未コミット値の 2 バージョンを保持する仕組み)を一般化し、複数の in-progress トランザクションがそれぞれ異なる時点のスナップショットを必要とするため、コミット済みの複数バージョンを併存させる。MVCC の核心的な性能特性は「読み取りは書き込みをブロックせず、書き込みは読み取りをブロックしない」ことである。(Source: [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]] "Multiversion concurrency control") ## PostgreSQL における実装 PostgreSQL の MVCC は各行に `inserted_by`(挿入したトランザクションの txid)と `deleted_by`(削除を要求したトランザクションの txid、未削除なら空)を持たせる。更新は内部的に「削除 + 挿入」に変換される。同一行の複数バージョンはヒープ内でリンクリストを形成する。(Source: [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]] "Multiversion concurrency control") **可視性ルール**(あるトランザクションがどの行バージョンを見られるか): 1. トランザクション開始時点で進行中(未コミット・未中断)だった他のトランザクションの書き込みは無視する 2. 自分より後に開始したトランザクションの書き込みは、コミット済みかどうかによらず無視する 3. 中断したトランザクションの書き込みは無視する(即座に削除する必要はなく GC が後で回収する) 4. それ以外の書き込みはすべて可視 言い換えると、ある行が見えるのは「(a) 挿入したトランザクションが自分の開始時点でコミット済みで、(b) 削除されていないか、削除トランザクションが自分の開始時点で未コミットだった」場合である。(Source: [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]] "Visibility rules for observing a consistent snapshot") インデックスは各エントリが行の特定バージョン(最古または最新)を指し、クエリはそこから可視なバージョンを辿って探す。CouchDB・Datomic・LMDB は別方式として、page を上書きしないイミュータブル(copy-on-write)な B-Tree で、書き込みのたびに新しい木のルートを作りそのルート自体が一貫したスナップショットになる。(Source: [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]] "Indexes and snapshot isolation") ## naming confusion(命名の混乱) SQL 標準は 1975 年の System R の定義に基づき、スナップショット分離が発明される前に repeatable read を定義した。そのためスナップショット分離を実装する DB は標準準拠を主張するため「repeatable read」を名乗ることが多いが、実際の意味はベンダーごとに異なる: PostgreSQL の repeatable read はスナップショット分離そのものだが、MySQL(InnoDB)の repeatable read はスナップショット分離より弱い保証しか持たず、IBM Db2 の repeatable read は直列化可能性を意味する。Oracle は「serializable」を名乗る分離レベルが実際にはスナップショット分離である。(Source: [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]] "Snapshot isolation, repeatable read, and naming confusion") ## 分散環境への拡張(Percolator) Percolator は Bigtable 上にトランザクション API を実装するライブラリであり、単一ノードのスナップショット分離を複数パーティション(Bigtable の行・列)にまたがる分散環境へ拡張する。単一ノード実装(PostgreSQL の txid ベース可視性ルール)が「トランザクション ID の単調増加」だけで完結するのに対し、Percolator は (1) クラスタ全体で単調増加する**タイムスタンプオラクル**への 2 回の問い合わせ(開始時・コミット時)、(2) 複数セルにまたがる書き込みをアトミックに可視化するための**クライアント駆動の 2PC**(prewrite でロック取得・プライマリ指定、commit でロック解放・書き込みメタデータ更新)という 2 つの追加機構を必要とする。データレコード・書き込みメタデータ・列ロックはいずれも Bigtable の条件付き変更 API 上に格納される。(Source: [[@2021__OReillyJapan__詳説 データベース - Chapter 13 分散トランザクション]] §13.7) ## 横断的知見 - **単一ノードの MVCC と分散環境の MVCC は「バージョン管理」という核は共有するが、可視性判定の基準が質的に異なる**: PostgreSQL(→ [[@2021__OReillyJapan__詳説 データベース - Chapter 5 トランザクション処理とリカバリ]] §5.3.6, [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]])はローカルなトランザクション ID の前後関係だけで可視性を判定できるが、Percolator(→ [[@2021__OReillyJapan__詳説 データベース - Chapter 13 分散トランザクション]] §13.7)は複数ノードにまたがる書き込みの可視性を単一の基準で揃える必要があるため、グローバルに単調増加するタイムスタンプオラクルという集中管理コンポーネントを新たに要求する。分散版が単一ノード版に対して支払う追加コストは、ロック機構そのものではなく「時刻の単一の真実源(single source of truth)をどう確保するか」に集約される。(Source: [[@2021__OReillyJapan__詳説 データベース - Chapter 5 トランザクション処理とリカバリ]] §5.3.6, [[@2021__OReillyJapan__詳説 データベース - Chapter 13 分散トランザクション]] §13.7) - **first committer wins の判定は単一ノードでもPercolatorでも同じ原則だが、判定の実装粒度が異なる**: 詳説 データベース5章のスナップショット分離は「同じ値を変更しようとした2トランザクションのうち先にコミットしようとしたほうのみ許可」という原則を持つ(→ [[ACIDと分離レベル]])。Percolator の prewrite フェーズは、書き込み対象セルの未解放ロックや新しいタイムスタンプの既存データを検出した時点で即座に中断する。両者は「競合検出をコミット試行の早い段階で行い、負けたトランザクションを中断・再試行させる」という同じ設計原則を、単一ノードのロック/タイムスタンプ順序付けと、分散環境の列ロック+タイムスタンプオラクルという異なる粒度の機構で実現している。(Source: [[@2021__OReillyJapan__詳説 データベース - Chapter 5 トランザクション処理とリカバリ]] §5.3.4, [[@2021__OReillyJapan__詳説 データベース - Chapter 13 分散トランザクション]] §13.7) - **実装寄りの教科書(DDIA)と抽象寄りの教科書(詳説 データベース)が MVCC を異なる抽象レベルで補完的に説明する**: DDIA は PostgreSQL の `inserted_by`/`deleted_by`(txid ベース)という具体的な可視性ルール実装を示すのに対し、詳説 データベース5章§5.3.6は「単調に増加するトランザクションIDまたはタイムスタンプを使うことで、コミットされたバージョンとコミットされていないバージョンを区別し、その時点で最大1つの未コミット値を持つことを目的とする」というプロトコルレベルの一般定義にとどめ、「ロック機能・スケジュール機能・競合解決技術(二相ロックやタイムスタンプ順序付けなど)を組み合わせて実装できる」と実装の自由度を明示する。両者を合わせると、DDIA の txid ベース設計は詳説 データベースが述べる「実装できる」複数の手段のうちの1つの具体例だと位置づけられる。(Source: [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]] "Multiversion concurrency control", [[@2021__OReillyJapan__詳説 データベース - Chapter 5 トランザクション処理とリカバリ]] §5.3.6) - **スナップショット分離が「ロストアップデートは防ぐがライトスキューは防がない」という性質は、独立した2つのソースが一致して述べる**: 詳説 データベース5章§5.3.4は「2つのトランザクションが同じ値を変更しようとした場合、先にコミットしようとしたほうのみがコミットを許可され、これによりロストアップデートのアノマリーを排除する」一方「スナップショット分離のもとでは、ライトスキューのアノマリーが発生する可能性がある」と明記する。これは DDIA の Table 8-1(スナップショット分離行: 更新のロスト=実装依存、書き込みスキュー=可能)と完全に整合する。ただし詳説 データベースはロストアップデート防止を「実装依存」ではなく無条件の性質として述べており、DDIA が言及する「MySQL/InnoDB の repeatable read は更新のロストを自動検出しない」という実装間のばらつき(→ [[ロストアップデートと書き込みスキュー]])までは踏み込んでいない。教科書の一般原理(詳説 データベース)と実装間のばらつきの実例(DDIA)という役割分担が読み取れる。(Source: [[@2021__OReillyJapan__詳説 データベース - Chapter 5 トランザクション処理とリカバリ]] §5.3.4, [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]] Table 8-1) ## 未解決の問い - naming confusion(PostgreSQL の repeatable read = スナップショット分離、MySQL の repeatable read ≠ スナップショット分離)は、実運用でどれだけの障害・データ不整合の原因になっているか。事例研究はあるか。 - イミュータブル B-Tree(CouchDB・Datomic・LMDB 方式)と txid ベースの可視性ルール(PostgreSQL 方式)は、書き込みスループットとストレージオーバーヘッドのどちらで優劣が付くか。B-Tree の一般的な設計(→ [[B-Tree]])との接続はどう整理できるか。 - MVCC のガベージコレクション(不要になった行バージョンの回収)のタイミング戦略は、長時間実行トランザクションの存在下でどうスケジューリングされるべきか。 - 詳説 データベースが述べる「ロックベース(二相ロック)またはロックなし(タイムスタンプ順序付け)で MVCC を実装できる」という一般論のうち、タイムスタンプ順序付けによる MVCC 実装の具体例は本 wiki にまだない。PostgreSQL 以外(タイムスタンプ順序付け方式を採る DB)の実装例を追加調査すべきか。 - Percolator のタイムスタンプオラクルは単一の集中コンポーネントであり、単一障害点・スループットのボトルネックになりうる。詳説 データベース13章はこの点への対処(オラクルの複製・高可用化)に触れていない。TiDB(Percolator モデルの実装例)はこの問題をどう解決しているか。 - PostgreSQL の txid ベース可視性ルール(単一ノード)と Percolator のタイムスタンプオラクル+列ロック(分散)を、単一の可視性ルールの特殊ケースとして統一的に記述できるか。「ローカルなトランザクション順序」と「グローバルなタイムスタンプ順序」を同じ形式で表現する試みは本 wiki にまだない。 ## 関連 - ソース: [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]] / [[@2021__OReillyJapan__詳説 データベース - Chapter 5 トランザクション処理とリカバリ]] / [[@2021__OReillyJapan__詳説 データベース - Chapter 13 分散トランザクション]] - 概念: [[ACIDと分離レベル]] / [[ロストアップデートと書き込みスキュー]] / [[直列化可能性]] / [[B-Tree]] / [[分散トランザクション]] - 実体: [[PostgreSQL]] / [[Percolator]] / [[Bigtable]] ## 出典 - [[@2026__OReilly__Designing Data-Intensive Applications 2E - Chapter 8 Transactions]]("Snapshot Isolation and Repeatable Read", "Multiversion concurrency control", "Visibility rules for observing a consistent snapshot", "Indexes and snapshot isolation", "Snapshot isolation, repeatable read, and naming confusion") - [[@2021__OReillyJapan__詳説 データベース - Chapter 5 トランザクション処理とリカバリ]](§5.3.4 スナップショット分離、§5.3.6 マルチバージョン同時実行制御の一般定義) - [[@2021__OReillyJapan__詳説 データベース - Chapter 13 分散トランザクション]](§13.7 Percolator によるスナップショット分離の分散環境への拡張)