はじめに
ちょっと機会があり、Isolation Level などを復習したので備忘として残すことにしました。
深いところまではふれません。去年受けたデータベーススペシャリスト試験の参考書をまとめなおしただけです。
本記事では、以下の内容に触れます:
トランザクションと隔離性の基礎
トランザクションとは
トランザクション(Transaction) とは、データベースに対する一連の操作をまとめた単位のことです。
例えば、銀行の振込処理を考えてみましょう:
-- AさんからBさんへ1000円振り込む処理
BEGIN; -- トランザクション開始
UPDATE accounts SET balance = balance - 1000 WHERE name = 'A'; -- Aさんの残高を減らす
UPDATE accounts SET balance = balance + 1000 WHERE name = 'B'; -- Bさんの残高を増やす
COMMIT; -- トランザクション確定
この 2 つの UPDATE 文はどちらも成功するか、どちらも失敗するかのどちらかでなければなりません。途中で失敗した場合はROLLBACKで元に戻します。
ACID 特性
トランザクションはACID 特性と呼ばれる 4 つの性質を持ちます:
- A (Atomicity / 原子性): すべて成功するか、すべて失敗するか
- C (Consistency / 一貫性): データの整合性が保たれる
- I (Isolation / 隔離性): 複数のトランザクションが互いに影響しない ← 本記事の焦点
- D (Durability / 永続性): 確定したデータは失われない
この中で特に重要なのが 隔離性(Isolation) です。
複数のトランザクションが同時に実行される際、お互いにどれだけ「見えない」ようにするかを制御するのが隔離性です。しかし、完全に隔離するとパフォーマンスが低下するため、データベースは 隔離性水準(Isolation Level) という概念で、隔離の度合いを調整できるようにしています。
データ不整合の代表的な 3 つのパターン
隔離性が不十分だと、以下のようなデータ不整合が発生してしまいます。
1. ダーティリード(Dirty Read)
ダーティリードとは、他のトランザクションがまだコミットしていない(確定していない)データを読み取ってしまう現象です。
具体例:銀行振込のシナリオ
| 時刻 | トランザクション A(振込処理) | トランザクション B(残高照会) |
|---|---|---|
| t1 | BEGIN; | |
| t2 | UPDATE accounts SET balance = balance - 1000 WHERE name = 'A'; | |
| t3 | SELECT balance FROM accounts WHERE name = 'A'; | |
| t4 | → 減額後の残高を読み取る(未コミット) | |
| t5 | ROLLBACK; (処理失敗) | |
| t6 | → 読み取ったデータは無効だった! |
問題点: トランザクション B が、実際には存在しないデータ(ロールバックされる予定のデータ)を読み取ってしまった。
2. ノンリピータブルリード(Non-Repeatable Read)
ノンリピータブルリードとは、同じトランザクション内で同じデータを 2 回読んだときに、異なる値が返ってくる現象です。
ファジーリード(Fuzzy Read)とも呼びます。
具体例:在庫確認のシナリオ
| 時刻 | トランザクション A(在庫確認) | トランザクション B(在庫更新) |
|---|---|---|
| t1 | BEGIN; | |
| t2 | SELECT stock FROM products WHERE id = 1; | |
| t3 | → 在庫: 100 個 | |
| t4 | BEGIN; | |
| t5 | UPDATE products SET stock = 50 WHERE id = 1; | |
| t6 | COMMIT; (確定) | |
| t7 | SELECT stock FROM products WHERE id = 1; | |
| t8 | → 在庫: 50 個(値が変わった!) | |
| t9 | COMMIT; |
問題点: トランザクション A 内で同じ商品の在庫を 2 回確認したのに、1 回目は 100 個、2 回目は 50 個と異なる結果になりました。
3. ファントムリード(Phantom Read)
ファントムリードとは、同じトランザクション内で範囲検索を 2 回実行したときに、異なる行数が返ってくる現象です(行が「幽霊のように」現れたり消えたりする)。
具体例:売上集計のシナリオ
| 時刻 | トランザクション A(集計処理) | トランザクション B(新規注文) |
|---|---|---|
| t1 | BEGIN; | |
| t2 | SELECT COUNT(*) FROM orders WHERE date = '2025-01-01'; | |
| t3 | → 結果: 10 件 | |
| t4 | BEGIN; | |
| t5 | INSERT INTO orders (date, amount) VALUES ('2025-01-01', 5000); | |
| t6 | COMMIT; (確定) | |
| t7 | SELECT COUNT(*) FROM orders WHERE date = '2025-01-01'; | |
| t8 | → 結果: 11 件(増えた!) | |
| t9 | COMMIT; |
問題点: トランザクション A 内で同じ条件で集計を 2 回実行したのに、1 回目は 10 件、2 回目は 11 件と異なる結果になりました。
ノンリピータブルリードとの違い
- ノンリピータブルリード: 既存の行の値が変わる(UPDATE)
- ファントムリード: 行の数が変わる(INSERT/DELETE)
データベースの隔離性水準(ISOLATION LEVEL)
これらのデータ不整合を防ぐために、データベースは 4 段階の 隔離性水準(Isolation Level) を提供しています。
1. READ UNCOMMITTED(最も緩い)
特徴:
- 他のトランザクションの未コミットデータを読み取れる
- 最も高速だが、データの正確性は保証されない
防げる不整合:
- なし(すべての不整合が起こってしまう。)
発生する不整合:
- ダーティリード
- ノンリピータブルリード
- ファントムリード
使用場面:
- 速度が最優先で、多少の不正確さは許容できる場合
- 例:アクセスログ、統計データの概算
2. READ COMMITTED
特徴:
- コミット済みのデータのみ読み取れる
- 多くのデータベースのデフォルト設定(PostgreSQL、Oracle など)
防げる不整合:
- ダーティリード
発生する不整合:
- ノンリピータブルリード
- ファントムリード
使用場面:
- 一般的な Web アプリケーション
- ダーティリードは避けたいが、パフォーマンスも重視したい場合
3. REPEATABLE READ
特徴:
- トランザクション開始時に読み取ったデータは、同じトランザクション内で再度読んでも同じ値が返る
- MySQL のデフォルト設定
防げる不整合:
- ダーティリード
- ノンリピータブルリード
発生する不整合:
- ファントムリード
使用場面:
- 同じデータを複数回読み取る必要がある処理
- 例:複雑な集計処理、レポート生成
4. SERIALIZABLE(最も厳しい)
特徴:
- 複数のトランザクションを実行しても完全に直列化される(並列実行されない)
- 最も安全だが、排他待ち起きやすくなりトランザクションのスループットが著しく低下する
防げる不整合:
- ダーティリード
- ノンリピータブルリード
- ファントムリード
発生する不整合:
- なし(すべての不整合を防ぐ)
使用場面:
- データの整合性が最優先の場合
- 例:金融取引、在庫の厳密な管理、会計処理
隔離レベル比較表
4 つの隔離レベルと 3 つの不整合の関係を表にまとめました:
| 隔離レベル | ダーティリード | ノンリピータブルリード | ファントムリード | パフォーマンス | データ整合性への耐性 |
|---|---|---|---|---|---|
| READ UNCOMMITTED | 発生する | 発生する | 発生する | ⭐⭐⭐ | ❌ |
| READ COMMITTED | 発生しない | 発生する | 発生する | ⭐⭐ | ⭐ |
| REPEATABLE READ | 発生しない | 発生しない | 発生する | ⭐ | ⭐⭐ |
| SERIALIZABLE | 発生しない | 発生しない | 発生しない | ❌ | ⭐⭐⭐ |
ポイント:
- 上に行くほど高速だが不整合が起きやすい
- 下に行くほど安全だが遅くなる
- READ COMMITTEDが多くの DBMS のデフォルト(バランス型)
実務での使い分けのポイント
1. どの隔離レベルを選ぶべきか
基本方針:
- 用途に応じた設定を検討する
- 金融取引などのミッションクリティカルなシステムの処理は特に要注意
用途別の推奨例:
| 用途 | 推奨レベル | 理由 |
|---|---|---|
| 一般的な Web アプリ | READ COMMITTED | パフォーマンスとデータ整合性のバランスが良い |
| レポート生成・集計処理 | REPEATABLE READ | 同じデータを複数回読む必要があるため |
| 金融取引・在庫管理 | SERIALIZABLE | データの厳密な整合性が必須 |
| アクセスログの集計・統計概算 | READ UNCOMMITTED | データの一貫性は保証されないが速度を優先したい場合適している |
2. パフォーマンスとデータ整合性のトレードオフ
隔離レベルを上げると:
- メリット: データの整合性が向上
- デメリット: ロック待ち時間が増え、スループットが低下
実務での対処法:
- トランザクションをできるだけ短くする
- 必要な部分だけ隔離レベルを上げる
3. 主要 DBMS のデフォルト設定
| DBMS | デフォルト隔離レベル |
|---|---|
| PostgreSQL | READ COMMITTED |
| MySQL (InnoDB) | REPEATABLE READ |
| Oracle | READ COMMITTED |
| SQL Server | READ COMMITTED |
メモ:
- MySQL のデフォルトは他より厳しい(REPEATABLE READ)
まとめ
重要ポイントの再確認
-
データ不整合は隔離性の問題
- 複数のトランザクションが同時実行されると発生
- ダーティリード、ノンリピータブルリード(ファジーリード)、ファントムリードの 3 種類
-
隔離レベルは 4 段階
- READ UNCOMMITTED(緩い)
- READ COMMITTED(バランス型・多くの DBMS のデフォルト)
- REPEATABLE READ(厳しい)
- SERIALIZABLE(最も厳しい)
-
トレードオフを理解する
- 高い隔離レベル = 安全だが遅い
- 低い隔離レベル = 高速だが不整合のリスク
- 用途に応じて適切なレベルを選択
次のステップ
データベースの制御についてもうちょっと詳しくやりたいなぁという方は下記のようなトピックがおすすめです:
- ロック機構: 楽観的ロック vs 悲観的ロック
- デッドロック: 発生原因と対処法
- MVCC(Multi-Version Concurrency Control): PostgreSQL や MySQL の内部実装
- インデックスとパフォーマンス: 隔離レベルとクエリ最適化の関係
データベースの基礎を理解することで、より堅牢で高性能なアプリケーションを構築できるようになります。ぜひ実際のプロジェクトでも意識して使ってみてください。