コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
ACID 特性
「巻き戻します」と言われても、信じる根拠がない
途中で落ちたトランザクションは再起動時に巻き戻される、と前の回で書きました。しかし電源が抜けた瞬間、メモリの中身は消えます。何を手がかりに「どこまでが確定で、どこからが未確定か」を判断しているのでしょうか。
答えは、本体を書き換える前にログを書いているからです。DB は更新を頼まれると、まず「この行をこう変える」という記録をログへ書きます。順番は次のとおりです。
- 変更内容をログに書く
- COMMIT のとき、ログに「ここまで確定」の印を書く
- その印がディスクに届いたことを確認してから、アプリに成功を返す
- 本体のデータファイルへは、その後で反映する
再起動時にはこのログを頭から読み直し、確定の印があるものだけ本体へ反映し、印のないものは捨てます。だから、どのタイミングで落ちても半端な状態が残りません。原子性 (Atomicity) と永続性 (Durability) は、この 1 本のログで同時に支えられています。
順番が逆でない理由もはっきりしています。本体のデータファイルへの書き込みは、あちこちのページを書き換えるのでディスクの上を飛び回ります。ログは末尾に足していくだけなので、順番に書けます。返事を早く返したい部分だけを順次書き込みで済ませ、飛び回る方は後回しにする、という分担です。
落ちなくても、2 人来ると壊れる
残る 2 つは、何も落ちていないときの話です。
在庫が 1 個の商品を 2 人が同時に買おうとしたとします。どちらのトランザクションも「在庫は 1 個ある」と読み、どちらも 1 個減らして、在庫は -1 になります。片方ずつ順番に実行していれば起きなかった結果です。これを防ぐのが分離性 (Isolation) で、実現には行を待たせるか、更新前の値を別に取っておくかのどちらかが要ります。
一貫性 (Consistency) は、トランザクションが終わった時点で、DB に宣言したルールが全部守られていることです。主キーの重複、参照先のない外部キー、残高がマイナスになる更新。これらが残るくらいなら、DBMS はコミットを失敗させて全部巻き戻します。
SQL クエリ
ALTER TABLE accounts ADD CONSTRAINT chk_balance CHECK (balance >= 0);ただし DBMS が見張れるのは、こうして宣言されたルールだけです。「出金額と入金額は一致する」のような業務の決まりごとは、アプリ側の責任として残ります。
速さのために、どれを弱めるか
4 つを厳密に守るほど遅くなります。いちばん効くのは、COMMIT のたびにログをディスクへ押し出す処理です。MySQL の innodb_flush_log_at_trx_commit を 1 から 2 に変えると、書き出しは OS のキャッシュまでで済み、ディスクへの反映は約 1 秒に 1 回にまとまります。MySQL のプロセスが落ちただけならデータは残りますが、サーバごと電源が落ちると直前の約 1 秒分のコミットが消えます。
つまみを回すこと自体は悪ではありません。危ないのは、何を捨てたかを知らずに回すことです。消えても翌日集計し直せるログと、消えたら返金が必要な決済とで、同じ設定を使う理由はありません。