インデックスの仕組み
100 万行の orders から、user_id が 42 の行を探します。インデックスが無いと、データベースは 1 行目から最後まで全部読んで、42 かどうかを 100 万回確かめます。数十件を取り出すための作業としては、明らかに多すぎます。
索引の無い本から、1 語を探している
厚い技術書で「トランザクション」という語を探すとき、1 ページ目からめくる人はいません。巻末の索引を引いて、そこに載っているページだけを開きます。索引は語が五十音順に並んでいるので、目当ての語まで数秒で辿り着けます。
データベースのインデックスは、これとまったく同じものです。列の値を並べ替えた見出しの一覧を、本体とは別に持っておきます。探すときは見出しのほうを引き、見つかった場所だけ本体を読みます。全ページ読む必要が消えます。
並んでいるから、半分ずつ捨てられる
見出しが並んでいると、真ん中を 1 つ見るだけで「探している値は前半か後半か」が決まります。これを繰り返すと、確かめる回数が一気に減ります。実際のインデックスは、この絞り込みを段に分けた木の形で持っています。
上から順に「どの枝か」を選ぶだけで、いちばん下に着いたときには候補が数十行まで絞れています。1 つの節から出る枝が多いほど、少ない段数で多くの行を扱えます。枝が 100 本なら 3 段で 100 万行、4 段で 1 億行に届きます。行が 100 倍に増えても段が 1 つ増えるだけ、というのがインデックスの効きどころです。
前から順にしか辿れない
索引は語の先頭から並んでいるので、「途中に『ザクション』を含む語」は索引では探せません。インデックスも同じで、次のような書き方をすると使われません。
SQL クエリ
-- 列に関数を被せると、並んでいるのは元の値なので辿れない
SELECT id, name FROM users WHERE LOWER(email) = 'taro@example.com';
-- 前方が分からない検索も辿れない
SELECT id, name FROM users WHERE name LIKE '%田中';
-- 文字列の列に数値を渡すと、暗黙の型変換で並び順の前提が崩れる
SELECT id, name FROM users WHERE user_code = 12345;効いているかどうかは推測せず、EXPLAIN を頭に付けて確かめます。
SQL クエリ
EXPLAIN SELECT id, ordered_at FROM orders WHERE user_id = 42;type が ALL、key が NULL と出ていれば、インデックスは使われず全件読んでいます。
遅いクエリを見つけたら、まず
EXPLAINです。インデックスを足す前に、既にあるインデックスを避ける書き方をしていないかを見るほうが、早く終わることが多くあります。
テーブル構造
CREATE TABLE btree_levels (
level INT PRIMARY KEY,
fanout INT NOT NULL
);
INSERT INTO btree_levels VALUES
(1, 100),
(2, 100),
(3, 100),
(4, 100);期待される出力
| level | reachable_rows |
|---|---|
| 1 | 100 |
| 2 | 10000 |
| 3 | 1000000 |
| 4 | 100000000 |