はじめに
RDB を「なんとなく整合性を保ってくれる便利なやつ」として使っている人は多いと思います。でも「じゃあ ACID の 4 文字を具体例つきで説明してみて」と言われると、意外と言葉に詰まる。自分もそうでした。
この記事では、ACID (Atomicity / Consistency / Isolation / Durability) を、Excel をデータベース代わりに使って回している架空 EC サイトで起きる事故を題材にして、1 文字ずつ具体的に説明します。「Excel だとこう壊れる、RDB だとこう防げる」という対比で追いかけると、各文字が指しているものがくっきりします。
RDB 設計の話にもう少し体系的に踏み込みたい方には 達人に学ぶ DB 設計徹底指南書 第 2 版 を、この記事で扱うような事故カタログの実例集としては 失敗から学ぶ RDB の正しい歩き方 をおすすめします。特に後者はこの記事と相性がよく、「実務でどこで踏むか」の解像度がぐっと上がります。
読み終わったあとで「本当に理解できたか」を腕試しできる教材として、 7 つの違和感を意図的に仕込んだ受注管理 Excel からACIDを学ぶサイト を用意しています。ACID を学んだあとで再訪してみると、どの違和感がどの文字に対応しているか自然に見えるはずです。
ACID は「トランザクションが満たすべき性質」の頭文字
ACID は複雑な仕組みの名前ではなく、トランザクション (複数の更新をひとまとまりで扱う単位) が満たすべき 4 つの性質の頭文字を並べただけです。
| 頭文字 | 性質 | ざっくり |
|---|---|---|
| A | Atomicity (原子性) | 全部やるか、全部やらないか |
| C | Consistency (一貫性) | 事前・事後で整合性が保たれる |
| I | Isolation (分離性) | 同時に走っても直列に走ったのと同じ結果 |
| D | Durability (永続性) | コミットしたら障害があっても消えない |
順に見ていきます。
A: Atomicity ── 注文だけが残った夜
EC の受注処理は、素朴に書いても最低 2 つの更新を伴います。
-
ordersに注文行を挿入する -
products.stockを減算する
この 2 つの間で処理が落ちたら何が起きるか。Excel マクロで組んでいた場合、注文シートには 1 行だけ増えて、在庫は減っていない状態がそのまま残ります。翌日の受注もこの実態と食い違う在庫を見て通ってしまうので、気付いた頃には売れないはずの数だけ注文が積み上がっている、というわりと洒落にならない事故になります。
RDB では 2 つの更新を 1 つのトランザクションにまとめて、どちらかが失敗したら両方なかったことにできます。
BEGIN;
INSERT INTO orders (id, customer_id, product_id, qty) VALUES (...);
UPDATE products SET stock = stock - 1 WHERE id = 'P-042';
COMMIT; -- ここまで到達しなければ INSERT も UPDATE も残らない
これが Atomicity です。「全部成功か、全部なかったことにするか」の 2 択しかない、という性質を DBMS が実装として持ってくれているので、アプリ側で「片方だけ成功したケースをどう手当てするか」を毎回考えずに済みます。
C: Consistency ── 「山田太郎」が 4 人いるシート
Consistency は少しつかみどころのない文字なので、事故側から入ります。
Excel の顧客シートを開いたら、同じメールアドレスで「山田太郎」の行が 4 つできていました。同一人物が 4 回登録してしまったのか、たまたま同姓同名が 4 人集まったのか、シートを見ても判定できません。ポイント付与も退会処理も「どの行に対して行えばいいのか」が決まらず、そこから先の処理がすべて止まります。
もう 1 パターン。顧客シートから退会者 C-999 を削除したはずなのに、注文シートには C-999 宛の注文が 3 件残ったまま。この注文は誰宛にどう発送すればいいのか、Excel の側からは何もわかりません。
RDB はこの手の「壊れた状態」がそもそも書き込めないように、スキーマの側に制約を宣言できます。
- UNIQUE / 主キー: 同じメールアドレスの 2 回目の INSERT はエラーで弾かれる
-
外部キー (FOREIGN KEY):
orders.customer_idは必ずcustomers.idに存在する値でなければならない。存在しないC-999を書こうとするとエラー - NOT NULL / CHECK: 「メール列は空じゃダメ」「金額は 0 以上」といったルールをスキーマに書き込む
制約に違反するデータはそもそも受け付けないので、事後に「壊れた行を見つけて直す」ではなく、書き込み時点で不整合な状態への遷移が起きないことが保証されます。この「事前・事後を通じて一貫した状態が保たれる」性質が Consistency です。
Consistency は「トランザクション内で制約が満たされる」という言われ方と、「その制約を宣言的に持てる」という言われ方の両方をされます。実務で効いてくるのはほぼ後者なので、この記事では制約の側から説明しました。
I: Isolation ── 2 人が同時に書いて修正が消えた
経理担当の A さんと B さんが、同じ売上シートを別々に開いて、別々の行を修正して保存した。つもりでした。あとから保存した B さんの変更が、A さんの変更を上書きして消してしまった ── というのが典型的な Lost Update です。
同時実行で起きる事故はこれだけではありません。
- Dirty Read: 他トランザクションのコミット前の値を読んでしまう
- Non-repeatable Read: 同じ SELECT を 2 回発行したら、間に他人の UPDATE が入って結果が違う
- Phantom Read: 同じ範囲検索を 2 回発行したら、間に INSERT が入って行数が違う
RDB はロックや MVCC (多版同時実行制御) を使って、複数トランザクションが同時に走っても「1 つずつ順番に流したのと同じ結果」になることを保証します。どこまで厳密にやるかは分離レベルで切り替えられます。
| 分離レベル | 防げるもの |
|---|---|
| READ UNCOMMITTED | (ほぼ何も防がない) |
| READ COMMITTED | Dirty Read |
| REPEATABLE READ | + Non-repeatable Read |
| SERIALIZABLE | + Phantom Read (完全な直列化) |
PostgreSQL と MySQL (InnoDB) のデフォルトはそれぞれ READ COMMITTED / REPEATABLE READ です。厳しくすればするほど整合性は上がりますが、その分ロック競合が増えて遅くなるので、業務要件と相談して選ぶ、という設計判断が必要になります。
Excel の「保存」にはこの選択肢がありません。先勝ちも後勝ちも制御不能で、これが業務データを Excel に持たせてはいけない最大の理由の 1 つです。
D: Durability ── 停電で 8 時間分が消えた
Isolation とは別に、単純な障害耐性の話が Durability です。
深夜の停電から復電したあと Excel を開いたら、当日の受注がまるごと消えていた。最終保存は朝で、それ以降 8 時間分が全滅、という事故は珍しくありません。もっと厄介なのは、誰かが古いバックアップを開いて上書き保存してしまい、先月削除したはずの注文が最下行に復活するパターン。Excel は「保存ボタンを押した」ことと「本当に永続化された」ことを区別しないので、あとから正しい状態がどれか判定できません。
RDB は Write-Ahead Logging (WAL) で、変更を先にログとしてディスクへ同期書き込みしてから、実データに反映します。この順序が肝で、プロセスが落ちても電源が落ちても、次回起動時にログを再生 (ロールフォワード) すればコミット済みの状態が復元されます。逆に、コミット前に落ちた変更は UNDO で確実に取り消されます。
COMMIT が返ってきた瞬間に「もう消えない」ことが保証される。この 1 行が、Excel との一番わかりやすい違いです。
理解度チェック: この Excel、7 箇所どこが壊れているか指摘できますか?
ここまで読んだ内容を頭に入れた状態で、実際に壊れた Excel を見てみてください。架空 EC サイトの受注管理 Excel に、意図的に 7 つの違和感を仕込んであります。それぞれが ACID のどの性質の欠如に対応するかを言語化できるか、腕試ししてみてください。
もしもこの世界にRDBがなかったら (7 つの違和感 + 答え合わせ)
それぞれの違和感は、以下の 5 つのページで詳細を扱っています (ACID の C を実務観点で「一意性」と「参照整合性」に分けています)。
- 原子性 ── 注文だけが残った夜
- 同時実行制御 ── 2 人が同時に書いて修正が消えた
- 一意性 ── 「山田太郎」の行が 4 つある
- 参照整合性 ── 顧客 ID が消えた注文シート
- 永続性 ── 停電で全部消えた
- 総括 ── RDB が黙って守ってくれている 5 つの根本価値
「じゃあ NoSQL は?」と思った方へ
RDB 以外の選択肢 (Key-Value / ドキュメント / ワイドカラム / グラフ / ベクトル / 時系列 / 検索エンジン) を、それぞれ「データの形」「得意な検索」「クエリ例」で 1 枚にまとめた記事もあります。
【図解】NoSQL・グラフ・ベクトル…RDB以外のデータベース7種を一気に整理する
おわりに
ACID は「なんとなく整合性を保ってくれる」ではなく、トランザクションと宣言的制約という 2 本の柱で 4 つの性質を保証する仕組みの名前です。各文字を具体例つきで説明できるようになると、
- ここはトランザクションで囲むべきか、囲まなくてよいか
- この列は UNIQUE か、CHECK で足りるか
- 外部キー制約で守るか、アプリ側で担保するか
- NoSQL に寄せていい機能はどれか、RDB に残すべき機能はどれか
本記事には Amazon アソシエイトのリンクが含まれます。リンク経由で購入いただくと運営者に紹介料が入りますが、価格は変わりません。