AさんがBさんに1万円を振り込むとき、裏では2つの処理が動きます。Aさんの口座から1万円を引き落とす。Bさんの口座に1万円を入金する。
もし引き落としだけが済んで入金が失敗したら、1万円はどこにも存在しなくなります。逆に入金だけが確定して引き落としが失敗したら、お金が増えてしまいます。この2つは、両方そろって成立するか、どちらも起きなかったことにするかのどちらかでなければいけません。
全部か、ゼロか
トランザクションは、複数の処理をひとつのまとまりとして扱い、全部成功して確定する(コミット)か、全部を元に戻す(ロールバック)かのどちらかを保証する仕組みです。「途中までしか処理されていない」状態を作らないことで、データのつじつまを守ります。データベースを扱ううえで欠かせない考え方です。
途中で止まると困る処理は、身の回りにたくさんあります。
- ECサイトの注文。在庫を減らし、注文を記録し、決済を確定する
- 銀行や送金サービスでの入出金
- 予約システムで、空席を確かめてから、その席を確保する
- 複数の表(テーブル)にまたがるデータの更新
ACID という4つの約束
トランザクションが守るべき性質は、4つの頭文字をとって ACID と呼ばれます。
| 性質 | 意味 |
|---|---|
| Atomicity(原子性) | すべて完了するか、すべて取り消されるかのどちらか |
| Consistency(一貫性) | 処理の前後で、データのつじつまが合った状態を保つ |
| Isolation(独立性) | 同時に複数の処理が走っても、互いに干渉しない |
| Durability(永続性) | 完了した処理の結果は、障害が起きても消えない |
振込の例でいえば、引き落としと入金が片方だけ起きないのが原子性、同じ口座から同時に2件の振込が走っても残高が狂わないのが独立性です。
まとまりを大きくしすぎると
「全部まとめて守れば安全」とはいきません。トランザクションの間、関係するデータには鍵(ロック)がかかり、ほかの処理は順番を待たされます。まとまりを広げすぎたり、長い時間続けたりすると、その待ちが積み重なってデータベース全体が遅くなります。
さらに厄介なのが、デッドロックです。処理Aが処理Bの終わりを待ち、処理Bも処理Aの終わりを待つ。お互いが相手の鍵が外れるのを待ち続けて、どちらも進めなくなる状態です。
どこまでをひとつのまとまりにするかの設計が、システムの信頼性を左右します。失敗したときのロールバックも、アプリの側で明示的に書いておく習慣をつけておきましょう。
なお、トランザクションが守るのは処理の途中での失敗です。ディスクの故障などでデータそのものが壊れたときには、バックアップからの復元が必要になります。構造の変更を「適用するか、戻すか」で管理するマイグレーションも、同じ発想を持っています。