なぜトランザクションが必要か — 銀行口座の例
銀行で「AからBに1000円送金する」操作は、Aから1000円引くとBに1000円足すの2ステップ。 片方が成功して片方が失敗すると、世界から1000円が消失します。 この「複数の操作を1つの不可分な単位にまとめる」仕組みがトランザクションです。
BEGIN; -- トランザクション開始
UPDATE accounts SET balance = balance - 1000 WHERE id = 'A';
UPDATE accounts SET balance = balance + 1000 WHERE id = 'B';
COMMIT; -- 確定(すべて成功)
-- または ROLLBACK; でなかったことにする
-- PostgreSQLやOracleは全DMLが暗黙に begin される。
-- MySQLのInnoDBはデフォルトautocommit=1で、BEGINで明示的に始める。 ACID — Jim Gray が体系化した4つの性質
ACIDは1983年にAndreas ReuterとTheo Härderが提唱し、1993年のJim Gray & Reuter の名著 『Transaction Processing: Concepts and Techniques』で広く普及しました。 Jim Grayは「トランザクション処理の父」と呼ばれ、1998年にチューリング賞受賞。
| 頭字 | 名称 | 意味 |
|---|---|---|
| A | Atomicity (原子性) | すべて成功するか、何もしなかったことになるか。中途半端はない |
| C | Consistency (一貫性) | トランザクション前後でDBの整合性制約(PK/FK/CHECK)が守られる |
| I | Isolation (分離性) | 他のトランザクションの途中結果は見えない(レベルは設定可能) |
| D | Durability (永続性) | コミット後は停電で落ちても結果が消えない |
並行実行で起こる3つの異常
複数のトランザクションが同時に動くと、3つの古典的異常が発生する可能性があります。 これらを防ぐことがトランザクション管理の目的です。
| 異常 | 説明 | 例 |
|---|---|---|
| ダーティリード (Dirty Read) | 他トランザクションの未コミットのデータを読んでしまう | Tx1が UPDATE → Tx2 が読む → Tx1 が ROLLBACK → Tx2 は幻の値を見た |
| ノンリピータブルリード (Non-repeatable Read) | 同じ行を再度読むと値が変わっている | Tx1 が SELECT → Tx2 が UPDATE + COMMIT → Tx1 が再SELECTすると別の値 |
| ファントムリード (Phantom Read) | 同じWHERE条件で再検索すると行数が変わる | Tx1 が COUNT → Tx2 が INSERT + COMMIT → Tx1 が再COUNTすると行数増加 |
分離レベル — 4段階の厳しさ
SQL-92で標準化された分離レベルは4段階。どこまで厳しく保護するかで性能と安全性のトレードオフを取ります。
| 分離レベル | Dirty Read | Non-repeatable Read | Phantom Read | 典型用途 |
|---|---|---|---|---|
| READ UNCOMMITTED | ❌ 発生 | ❌ 発生 | ❌ 発生 | めったに使わない。統計的な概算用 |
| READ COMMITTED | ✅ 防止 | ❌ 発生 | ❌ 発生 | PostgreSQL/Oracleのデフォルト |
| REPEATABLE READ | ✅ 防止 | ✅ 防止 | ❌ 発生(SQL標準では) | MySQL InnoDB のデフォルト |
| SERIALIZABLE | ✅ 防止 | ✅ 防止 | ✅ 防止 | 金融・在庫・一貫性最優先 |
MVCC — 読み書きがブロックしない秘訣
伝統的には2PL(Two-Phase Locking, 2相ロック)で分離性を実装していました。 行を読むときは共有ロック、書くときは排他ロック、でコミット/ロールバックまで保持する方式。 しかし読み込みと書き込みが互いにブロックし、オンラインの大規模サービスには辛すぎました。
1980年代にMVCC(Multi-Version Concurrency Control, 多版同時実行制御)が登場。 「書き換え時に新しいバージョンを作り、読み手は自分のトランザクションが見るべきスナップショットを読む」方式で、 読み込みが書き込みをブロックしない画期的な仕組みを実現しました。
sequenceDiagram
participant T1 as Tx1 (Writer)
participant D as Data
participant T2 as Tx2 (Reader)
T1->>D: UPDATE x = 2 (古いx=1は残る)
Note over D: x の版<br/>v1: x=1 (Tx1開始前)<br/>v2: x=2 (Tx1によって作成, 未コミット)
T2->>D: SELECT x
D-->>T2: v1: x=1 (Tx2はTx1コミット前のスナップショット)
T1->>D: COMMIT
T2->>D: SELECT x (同じTx2内)
D-->>T2: v1: x=1 (Tx2は依然として自分のスナップショットを見る)
Note over T2: 反復可能読み取りが保証される| DB | MVCC実装 |
|---|---|
| PostgreSQL | タプル(行)ごとに xmin/xmax を持ち、古い版もテーブル内に残す。VACUUMで回収 |
| MySQL InnoDB | Undo Logに古い版を積む。クラスタインデックスが主版 |
| Oracle | Undo Segmentに古い版を積む。ORA-01555 (Snapshot Too Old) は有名なエラー |
| SQL Server | tempdbのVersion Storeに古い版を積む |
Serializable Snapshot Isolation (SSI)
MVCCで実装されるREPEATABLE READ相当(Snapshot Isolation, SI)には「Write Skew」という古典的な穴があります。 医師の当直シフトで、2人の医師が同時に「他に当直がいるから休める」と判断して両方とも休暇を取る、といった異常です。
これを解決するのがSSI(Serializable Snapshot Isolation)で、PostgreSQL 9.1 (2011年)で実装されました。 MVCCの高速性を維持しつつ、トランザクション間の読み書き依存を監視してSerializableに違反しそうな組み合わせで一方をアボートする方式です。
ロックの種類 — 共有・排他・意図
MVCCが普及してもロックは消えず、明示的なロックは今もアプリで頻用されます。
| 種類 | 構文 | 用途 |
|---|---|---|
| 共有ロック (Shared, S) | SELECT ... FOR SHARE | 他の読みはOK、書き込みは待たせる |
| 排他ロック (Exclusive, X) | SELECT ... FOR UPDATE | 他の読み書きを待たせる。更新前に取得 |
| ギャップロック (Gap Lock) | MySQL InnoDB 自動 | インデックス範囲にINSERTを防ぐ(ファントム防止) |
| 意図ロック (Intention Lock) | 自動 | テーブルレベルで「この先で行ロックを取る」意図を示す |
| アドバイザリロック (Advisory) | pg_advisory_lock(key) | アプリ定義の論理ロック、長時間保持可能 |
-- 在庫チェック→更新 のクラシックな排他制御パターン
BEGIN;
SELECT stock FROM products WHERE id = 42 FOR UPDATE; -- 行ロック
-- stock が十分あることを確認
UPDATE products SET stock = stock - 1 WHERE id = 42;
COMMIT;
-- ロック競合を待たずエラーにする(FOR UPDATE NOWAIT)
BEGIN;
SELECT * FROM products WHERE id = 42 FOR UPDATE NOWAIT;
-- 既にロックされていれば即エラー
-- 取れない行はスキップ(FOR UPDATE SKIP LOCKED) — ジョブキューで便利
SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED; 第8章のまとめ
- トランザクションは複数の操作を不可分な単位にまとめる仕組み。ACID(原子性/一貫性/分離性/永続性)で信頼性を保証
- 並行実行で発生する古典的異常はDirty Read / Non-repeatable Read / Phantom Read / Lost Update
- 分離レベルはREAD UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLEの4段階。PG/Oracle は RC、MySQL InnoDB は RR がデフォルト
- MVCCは読み書きをブロックしない多版方式。PG/MySQL/Oracle/SQL Serverすべてが採用。PGはVACUUMが必須
- SSI (Serializable Snapshot Isolation)はSnapshot IsolationのWrite Skew穴を埋める発明。PG 9.1/CockroachDB等で実装
- ロックは共有/排他/ギャップ/意図/アドバイザリ。
FOR UPDATE SKIP LOCKEDはジョブキュー実装の定番
次章では、ここまで学んだSQL機能がDBMSごとにどう異なるか、そしてSQL以外の選択肢(NoSQL、NewSQL、OLAP系、ORM論争)を整理します。 「どのDBを選ぶべきか」という実務の問いに向き合う章です。
理解度チェック
ACIDの「I」が意味するものはどれですか?
キーボード: 1〜4 で選択、Enter で回答