索引とB木 — なぜ一瞬で見つかるのか
100万件の表から1件を探すのに、上から順に全部見ていたら日が暮れます。索引(インデックス)は、探すためだけの「並べ替えた地図」を裏で持っておく工夫。その地図の正体が、データベースの心臓部 B+木です。全行スキャンとの速さの違い、木を根から降りる探索、あふれたノードが分割して育つ様子を、動かして理解します。
1. 索引なし vs 索引あり — 探す手間が桁違い
索引がなければ、DBは目的の行を見つけるまで先頭から1行ずつ全部確認します(フルスキャン)。件数 n に比例して遅くなる O(n) の世界です。いっぽう索引があると、並べ替え済みの地図を半分ずつ絞り込みながらたどれるので、確認回数は 約 log₂n 回で済みます。
下で同じ値を左右で同時に探させ、確認した回数を見比べてください。右の「索引を張る」を切り替えると、遅い全行スキャンが一気に速くなります。
2. B+木の探索 — 根から葉へ、範囲は葉のリンクで
DBの索引はただの並べ替えではなく、ディスク向けに設計された多分岐の木 B+木です。中間ノードは「この値より小さいなら左、大きいなら右」の道しるべ(区切りキー)だけを持ち、実際のデータは一番下の葉に集められています。探索は根から区切りキーと比べて枝を選び、葉まで一直線に降りるだけ。
さらに葉どうしは横に鎖でつながっています。だから「20〜60の範囲」のような範囲検索は、先頭の葉を見つけたら鎖をたどるだけで連続して読めます。下で探す値を変え、範囲スキャンも試してください。
3. あふれたら分割 — 挿入とノード分割
木はどうやって低いまま育つのでしょう。鍵はノード分割です。値を挿入すると、まず根から降りて正しい葉に差し込みます。その葉が入りきる上限を超えてあふれたら、ノードを2つに割り、真ん中のキーを1段上へ押し上げます。上の段もあふれれば同じことが連鎖し、根まで割れると木の高さが1段だけ伸びる——だから木は常にバランスを保ちます。
下でスライダーの値を追加してみてください。あふれた瞬間ノードが赤くなり、押し上げられたキーが上の段で光ります。
4. 浅くて広い木 = 少ないディスク読み込み
なぜ二分探索木ではなく、わざわざ多分岐の木を使うのでしょう。答えはディスクにあります。ディスクは1回の読み込みで1ページ(数KB)をまとめて運びます。そこで1ノードを1ページに詰め、数百個のキーを1ノードに持たせる——すると分岐数が数百になり、木は驚くほど浅くなります。木の高さ=ディスク読み込み回数なので、浅い木ほど速いのです。
下で分岐数と件数を動かし、木の高さ(=読み込み回数)がどれだけ縮むか確かめてください。
5. まとめ — 索引の設計図
- 索引 = 探すための並べ替えた副本:全行スキャン O(n) を O(log n) に変える。ただし書き込みと領域のコストと引き換え。
- B+木:中間ノードは道しるべ、データは葉に集約、葉は鎖でつながり範囲検索に強い。RDBの索引の定番。
- ノード分割:あふれたら中央値を上へ押し上げて2分割。木は常にバランスして高さ O(log n) を保つ。
- 多分岐=浅い木:1ノードに数百キーを詰めると高さが数段になり、ディスク読み込み回数が激減する。