🔩 ねじき教室 Go と Web の教室

インデックスで速くする

読了目安 約3分

スロークエリを EXPLAIN で確かめ、インデックスを張り、速くなったことを確認する手順を型にします。

この章の目次

インデックスはなぜ速いのか の実践編です。 ISUCON のお題アプリは、必要なインデックスが意図的に抜かれていることが多く、「張るだけで数倍」がよく起きます。 勘で張るのではなく、計測から確認までを型にします。

手順の型

  1. スロークエリログの集計で、合計時間が上位のクエリを特定する
  2. そのクエリを EXPLAIN にかけ、全行走査になっていることを確かめる
  3. ALTER TABLE ... ADD INDEX でインデックスを張る
  4. もう一度 EXPLAIN にかけ、インデックスが使われたことを確かめる
  5. ベンチを回してスコアで確かめる

EXPLAIN の読み方

EXPLAIN は、「MySQL がそのクエリをどう実行するつもりか」を表で返す命令です。 クエリの先頭に付けて使います。 まず見るのは type、rows、key の 3 列です。

前章の順位表で「600 回で 61 秒」だったクエリが、これだったとします。

SQL
SELECT * FROM posts WHERE user_id = 42 ORDER BY created_at DESC LIMIT 20;

結果はこうです(数値は例です)。

テキスト
mysql> EXPLAIN SELECT * FROM posts WHERE user_id = 42 ORDER BY created_at DESC LIMIT 20;
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows   | Extra                       |
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
|  1 | SIMPLE      | posts | ALL  | NULL          | NULL | NULL    | NULL | 985000 | Using where; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
  • type:行の探し方。ALL は全行走査(テーブルの全行を読んで条件に合うものを探すやり方)で、最悪の値
  • key:実際に使われたインデックス。NULL は「使えるものがなかった」
  • rows:読む見込みの行数。ここでは約 98 万行、つまりテーブル全体
  • ExtraUsing filesortORDER BY のための並べ替えを、取り出した行に対して別途実行している印

20 件返すために 98 万行を読んで並べ替えているわけです。

複合インデックスを張る

このクエリは WHERE user_id = ? で絞り、created_at の降順で並べています。 複数の列を順に並べた複合インデックスに、両方の列を入れます。

SQL
ALTER TABLE posts ADD INDEX idx_user_id_created_at (user_id, created_at);

列の順序が肝心で、等値で絞る user_id を先、並べ替えの created_at を後に置きます。 このインデックスでは user_id = 42 の区画に一気に飛べ、区画の中はすでに created_at 順なので並べ替えも不要になります。

張ったら、もう一度 EXPLAIN です。

テキスト
+----+-------------+-------+------+------------------------+------------------------+---------+-------+------+---------------------+
| id | select_type | table | type | possible_keys          | key                    | key_len | ref   | rows | Extra               |
+----+-------------+-------+------+------------------------+------------------------+---------+-------+------+---------------------+
|  1 | SIMPLE      | posts | ref  | idx_user_id_created_at | idx_user_id_created_at | 4       | const | 120  | Backward index scan |
+----+-------------+-------+------+------------------------+------------------------+---------+-------+------+---------------------+
  • type:ALL から ref(インデックスで特定の値に一致する行だけを読む探し方)に変わった
  • key:張ったインデックスの名前が入った
  • rows:98 万から 120 に落ちた
  • Extra:Using filesort が消えた。Backward index scan は、昇順のインデックスを逆向きに読んで降順を作っている印

type が refrange(範囲での絞り込み)になっていれば、インデックスは効いています。 確認できたらベンチを回し、順位表からこのクエリが落ちたことを見届けます。

張ったのに効かないとき

EXPLAIN の key が NULL のままなら、次の 2 つを疑ってください。

1 つ目は、列に関数を噛ませている場合です。

SQL
-- 効かない
SELECT * FROM posts WHERE DATE(created_at) = '2026-07-17';

インデックスは列の生の値の順に並んでいるので、関数を通した後の値では引けません。 生の値のまま範囲条件に書き換えれば効きます。

SQL
-- 効く
SELECT * FROM posts
WHERE created_at >= '2026-07-17' AND created_at < '2026-07-18';

2 つ目は、複合インデックスの左端ルールです。 (user_id, created_at) は、user_id を指定しない WHERE created_at > ? だけの検索には使えません。 姓、名の順で並んだ名簿から名だけで人を探せないのと同じで、左端の列(またはそこから連続する先頭部分)を条件に使うときだけ効きます。

インデックスは書き込みのたびに維持コストがかかるので、スロークエリの上位に出たクエリにだけ張ります。

次章は、クエリの回数そのものを減らす手、キャッシュです。