コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
インデックスチューニング
インデックスチューニング
このレッスンで分かること
- インデックスが効かなくなる典型的な 6 パターン
- 複合インデックスのカラム順を決める基準
- 不要なインデックスを見つけて削除する手順
インデックスチューニング とは
効くインデックス・効かないインデックスを見抜く。本レッスンでは、インデックスチューニング の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。
「インデックスが効いている」の判定
まず確認すべきは EXPLAIN の出力です。MySQL なら type=ref / range、key 欄に意図したインデックス名。PostgreSQL なら Index Scan や Index Only Scan。これらが出ていなければ、インデックスは使われていません。
インデックスが効かない 6 パターン
1. WHERE 句で関数や演算をしている
SQL クエリ
-- 効かない
WHERE LOWER(email) = 'alice@ex.com'
WHERE YEAR(created_at) = 2026
WHERE age + 1 > 30左辺が加工されると、インデックスのキー値と一致しないためです。
対策 は次の通りです。
SQL クエリ
-- データを統一して関数を外す
WHERE email = 'alice@ex.com' -- メアドは小文字で保存しておく
-- 範囲条件に書き換え
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01'
-- 関数インデックスを作る(PostgreSQL)
CREATE INDEX idx_lower_email ON users (LOWER(email));2. 暗黙の型変換
SQL クエリ
-- email カラムが VARCHAR なのに数値で検索
WHERE email = 12345DB は内部で CAST を掛け、結果として関数適用と同じ状態になり、インデックスが無効化されます。アプリ側で型を合わせましょう。
3. 前方一致以外の LIKE
SQL クエリ
WHERE name LIKE 'Alice%' -- 効く(前方一致)
WHERE name LIKE '%Alice%' -- 効かない(中間・後方一致)
WHERE name LIKE '%Alice' -- 効かない(後方一致)ソート済み B+tree は「左から見ていく」ため、左端が定まらないと無力です。後方一致が必要なら全文検索インデックス(FULLTEXT、GIN、Elasticsearch)を使います。
4. OR 条件
SQL クエリ
WHERE email = 'x' OR phone = '090-...'email インデックスも phone インデックスも別々にあれば、片方しか使われないか、UNION で書き換える必要があります。UNION に書き換えると両方のインデックスを使える場合があります(UNION ALL だと両条件に一致する行が重複するため UNION で重複排除が必要)。また MySQL では index merge (union) 最適化により、OR のままでも複数インデックスが使われることがあります。
5. NULL の扱い
PostgreSQL・MySQL(InnoDB) はいずれも NULL をインデックスに格納しており、WHERE col IS NULL でもインデックスが使われます。ただし複数カラムに IS NOT NULL を含む複雑な条件では、プランナがインデックスを選ばない場合があります。NULL を 0 や '' で代用するのは意味が変わるためアンチパターンです。NOT NULL 制約 が付けられるなら付けておくのが望ましい設計です。
6. カーディナリティが低い
性別カラム(M / F)にインデックスを貼ると、半分の行を返すクエリではインデックス参照より全表走査の方が速い。プランナがそう判断するとインデックスを無視します。これは正しい挙動です。
複合インデックスのカラム順
(a, b, c) の複合インデックスは「a でソート、同じ a 内で b でソート、同じ a, b 内で c でソート」されています。
| WHERE 条件 | 効くか |
|---|---|
| a = ? | 効く |
| a = ? AND b = ? | 効く |
| a = ? AND b = ? AND c = ? | 効く |
| a = ? AND c = ? | a だけ効く(b でフィルタ) |
| b = ? | 効かない |
| c = ? | 効かない |
| a = ? AND b > ? AND c = ? | a と b の範囲まで効く |
範囲条件(>、<、BETWEEN、LIKE 'x%')の後ろのカラムはインデックスのソート恩恵を受けません。順序を選ぶときは次の優先順位を意識します。
- 等値比較 を左に置く
- ソートキー を次に置く
- 範囲条件 は最後
不要なインデックスを見つける
過剰なインデックスは書き込みを遅くするコスト要因です。次のクエリで使われていないインデックスを見つけられます。
SQL クエリ
-- MySQL 8.0
SELECT * FROM sys.schema_unused_indexes;
-- PostgreSQL
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0;idx_scan = 0 が長期間続いているインデックスは削除候補です。ただし、月次バッチで使うインデックスは見落とさないように。
カバリングインデックスを意識
前のレッスンで触れた通り、SELECT 句のカラムまでインデックスに含めると テーブル参照 を消せます。読み取り頻度が極めて高いクエリには検討する価値があります。
インデックスは「クエリ駆動」で設計する。テーブルから考えるのではなく、頻出クエリから逆算する。
チューニングの流れ
- 遅いクエリを特定 —
slow_query_log(MySQL) やpg_stat_statements(PostgreSQL) - EXPLAIN で計画を確認 —
type=ALLやUsing filesortがないか - 頻出クエリを列挙 — 何種類の
WHERE / ORDER BY / GROUP BYパターンがあるか - 複合インデックスを設計 — 等値 → ソート → 範囲の順
- 削除候補も確認 — 使われていない索引を消す
- 計測 → 改善 — 本番に近いデータで検証
やってみよう
pg_stat_statementsかmysqldumpslowで頻度上位 10 クエリを抽出する- それぞれにベストな複合インデックスを設計し直す
- 1 ヶ月運用後に
idx_scan = 0のインデックスを削除する
よくある質問
Q. インデックスを貼ると遅くなる処理はありますか?
A. INSERT / UPDATE / DELETE は遅くなります。インデックス自体も更新が必要なため、書き込みが多いテーブルに大量のインデックスを貼ると書き込み性能が落ちます。読み取りと書き込みのバランスで判断し、不要なインデックスは削除しましょう。
Q. 複合インデックスは順番が大事ですか?
A. 順番が極めて重要です。(user_id, created_at) のインデックスは WHERE user_id = ? AND created_at > ? で効きますが、WHERE created_at > ? だけだと効きません。よく使う絞り込みカラムを先頭に置くのが定石です。
Q. インデックスが効いているか確認するには?
A. EXPLAIN(MySQL/PostgreSQL)または EXPLAIN ANALYZE で実行計画を見ます。type が ref / range / const なら使われていて、ALL は全件スキャン(インデックス未使用)です。意図と違うインデックスが使われている場合は USE INDEX で強制することもできます。
次のレッスン
次は 統計情報とカーディナリティ で、効くインデックス・効かないインデックスを見抜く を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- インデックスチューニング の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. インデックスチューニング とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン