トランザクションとは
銀行振込を想像してください。「口座Aから1万円を引き落とし、口座Bに1万円を入金する」という操作は、2つのSQL文で構成されます。
UPDATE accounts SET balance = balance - 10000 WHERE id = 'A';
UPDATE accounts SET balance = balance + 10000 WHERE id = 'B';
もし1文目の実行後、2文目の直前にシステムがクラッシュしたら? 1万円が宙に浮いて消えます。
これを防ぐのがトランザクションです。「2つの操作をひとまとめにして、全部成功するか全部なかったことにする」という仕組みです。
BEGIN;
UPDATE accounts SET balance = balance - 10000 WHERE id = 'A';
UPDATE accounts SET balance = balance + 10000 WHERE id = 'B';
COMMIT; -- 全部成功なら確定
-- ↑ いずれかが失敗すれば ROLLBACK(自動または手動)
ACID特性
トランザクションが保証すべき4つの性質をACIDと呼びます。
A — 原子性(Atomicity)
「全部成功か全部失敗」です。途中の状態は存在しません。
上記の振込例がまさにこれです。引き落としと入金は一体であり、片方だけが完了することはありません。
C — 整合性(Consistency)
トランザクション実行前後で、データが定義したルールを満たしている状態を保ちます。
例:「残高は0以上でなければならない」という制約がある場合、残高を負にするトランザクションは拒否されます。
I — 独立性(Isolation)
複数のトランザクションが同時実行されても、互いに影響を与えません。
あなたが残高を確認している最中に、別のトランザクションが金額を書き換えても、あなたの参照結果は一貫した状態を保ちます。
D — 耐久性(Durability)
コミットされたデータは、その後のシステム障害が起きても永続的に保持されます。
これを実現するのが**WAL(Write-Ahead Logging)**です。変更内容をデータファイルに書き込む前に、まずログファイルに記録します。障害後のリカバリ時にログを再適用して整合性を回復します。
分離レベルと発生する問題
独立性(I)は「完全な独立」を実現するほど性能が低下します。そのため、実用上は4段階の分離レベルから選びます。
分離レベルを下げると発生する問題
| 問題 | 内容 |
|---|---|
| ダーティリード | コミット前のデータを別トランザクションが読んでしまう |
| ノンリピータブルリード | 同一トランザクション内で同じ行を2回読むと、値が変わっている |
| ファントムリード | 同一トランザクション内で同じ条件で検索すると、行数が変わっている |
分離レベルの比較
| 分離レベル | ダーティリード | ノンリピータブル | ファントム |
|---|---|---|---|
| READ UNCOMMITTED | 発生 | 発生 | 発生 |
| READ COMMITTED | 防止 | 発生 | 発生 |
| REPEATABLE READ | 防止 | 防止 | 発生 |
| SERIALIZABLE | 防止 | 防止 | 防止 |
多くのRDB(PostgreSQLなど)のデフォルトは READ COMMITTED です。MySQLのInnoDBは REPEATABLE READ です。
実際の開発では、デフォルトの分離レベルを把握した上で、問題が起きうる箇所に SERIALIZABLE を適用するか、楽観的ロックを使って対処します。
ロックとデッドロック
独立性を保つ方法のひとつがロックです。あるトランザクションがデータを更新している間、他のトランザクションの読み書きをブロックします。
しかし、2つのトランザクションが互いに相手のロック解除を待ち続けるデッドロックが発生することがあります。
トランザクションA: テーブル1をロック → テーブル2を待機
トランザクションB: テーブル2をロック → テーブル1を待機
(永遠に進めない)
RDBはデッドロックを検出し、片方を強制ロールバックして解消します。
デッドロックを防ぐ実践的な対策:
- 複数テーブルを更新する順番を、アプリケーション全体で統一する
- トランザクションをできる限り短くする
- 更新が少ない参照系クエリには
SELECT ... FOR SHAREを使う
まとめ
| 用語 | ひと言まとめ |
|---|---|
| トランザクション | 一連の操作を一体として扱う仕組み |
| Atomicity | 全部成功か全部失敗 |
| Consistency | 前後でデータルールを守る |
| Isolation | 同時実行でも互いに影響しない |
| Durability | コミット後は障害でも失われない |
| 分離レベル | 独立性と性能のトレードオフを調整する設定 |
| デッドロック | 互いがロック待ちになる状態 |
「なぜ本番でたまにデータが壊れるのか」「なぜ特定の処理が止まるのか」という問題の多くは、トランザクションと分離レベルの理解で解決できます。
RDBの基本的な操作と設計についてはリレーショナルDBとSQL基礎で学べます。