← 研修トップ
データベースの仕組み
消したはずの行がなぜ残り、なぜ掃除が必要になるのか。落ちても壊れない仕組み、同時に触られても平気な仕組み、そして索引が使われない理由を、PostgreSQL と MySQL の公式文書の文言から積み上げます。広く教えられている説明が公式には無い領域でもあります。
- 1約 9 分なぜ古い行を消さないのか — 読む人と書く人がすれ違わないためにPostgreSQL は `UPDATE` や `DELETE` をしても、古い行をその場で消しません。公式はこの方式を多版同時実行制御の利益を得るために**必要である**と書いています。まだ他のトランザクションから見えている可能性のある行版を、消してはいけないからです。
- 2約 9 分その保証が効かないとき — 素のSELECTが止まる唯一の条件「PostgreSQL では読み取りがブロックされない」は言い過ぎです。公式は例外を1つだけ、はっきり書いています——**Only an `ACCESS EXCLUSIVE` lock blocks a `SELECT` (without `FOR UPDATE/SHARE`) statement.** 逆に読めば、**それ以外のロックでは素の `SELECT` は止まりません**。例外は1種類だけです。
- 3約 9 分掃除は4つの仕事をしている — そしてディスクは空かない`VACUUM` が必要な理由は、よく語られる2つ(不要行の回収・統計情報の更新)ではありません。PostgreSQL 18 の公式は "several reasons" として**4つ**を番号付きで列挙しています。残る2つを知らないと、なぜ何もしていない表まで掃除されるのかが説明できません。
- 4約 9 分ディスクを空けるコマンドが、作業中に追加のディスクを要求する`VACUUM FULL` は通常の `VACUUM` の「強い版」ではなく、**別物**です。公式は "There are two variants of `VACUUM`" と2つに分けた上で、`VACUUM FULL` は表の内容を**新しいファイルに書き直す**と説明します。だからOSに空間を返せます。
- 5約 9 分32ビットが尽きる日 — 止まるのではなく、書けなくなるトランザクションID(XID)は **32ビット**です。長く動き続けたクラスタでは番号が一周し、公式の言葉では "transactions that were in the past appear to be in the future — which means their output become invisible"。**過去が未来に見え、結果が見えなくなります。** 対策は `FrozenTransactionId` という特別なXIDで、常にどの通常XIDより古いと見なされます。
- 6約 9 分先に書くログ — 順序を縛ると、書く回数が減るデータベースは、テーブルの中身を書き換える前に「何をどう書き換えるか」を記録したログを先に永続化します。データファイルへの変更は **それを記述した WAL レコードが永続化された後でなければ書けない**。これが WAL の中核です。
- 7約 9 分どこまで戻ればいいか — 印を打つ代償チェックポイントは、復旧をどこから始めればよいかを決める**印**です。印があると、落ちたあとログを遡る範囲が限られます。印より前の変更は *guaranteed to be already on disk* ——すでにディスクに載っていると保証されるからです。
- 8約 9 分既定値が違う2つのDB — 同じSQLが、同じようには走らない同じSQLを書いても、**InnoDB は REPEATABLE READ、PostgreSQL は READ COMMITTED** で走ります。分離レベルは「自分が選ぶもの」だと思われがちですが、多くの現場では**誰も選んでいない既定値**のまま動いています。そしてその既定値が、製品によって逆側にずれています。
- 9約 9 分隙間に鍵をかける — next-key lock と、それが効く場面InnoDB は行だけでなく**行と行の隙間**にも鍵をかけます。公式は “To prevent phantoms, `InnoDB` uses an algorithm called next-key locking that combines index-row locking with gap locking.” と書いています。存在しない行の場所を押さえるので、他のトランザクションは**そこに挿入できません**。
- 10約 9 分ロックは行ではなく索引につく — WHEREで除外した行も巻き込むInnoDB のロックは行そのものではなく**インデックスレコード**に掛かります。公式は “Record locks always lock index records, even if a table is defined with no indexes.” と書いています。索引が無ければ隠しクラスタ化インデックスが作られ、それが使われます。
- 11約 9 分「B+木」を公式は使わない — 通称と原典の表記を分けるインデックスの構造は「B+木」と説明されることが多いですが、**MySQL も PostgreSQL も公式文書では B-tree と書きます**。InnoDB は “`InnoDB` indexes are B-tree data structures”、PostgreSQL は “the standard btree (multi-way balanced tree) index data structure” です。
- 12約 9 分索引が使われない理由 — 左端プレフィックスと、その反例複合インデックスは**左端プレフィックス**として使われます。`(col1, col2, col3)` なら `(col1)` `(col1, col2)` `(col1, col2, col3)` で引けます。ただし公式は**反例**も並べています——`WHERE last_name='Jones' OR first_name='John'` は、第1列を含んでいるのに**使われません**。
- 13約 9 分デッドロックは異常ではない — "Just try again."デッドロックは障害ではありません。公式はこう書いています——“Deadlocks are a classic problem in transactional databases, but they are not dangerous unless they are so frequent that you cannot run certain transactions at all.” そして対処の指示は3語です——**“Just try again.”**