Query Go
インデックスの罠 — 貼りすぎ・使われない・EXPLAIN 確認
チューニング

インデックスの罠 — 貼りすぎ・使われない・EXPLAIN 確認

インデックス過剰による書き込み劣化、未使用インデックスの検出、EXPLAIN 読解の基本。

インデックスの罠 — 貼りすぎ・使われない・EXPLAIN 確認 diagram

インデックスの貼りすぎは禁物

インデックスは書き込みのたびに更新されます。INSERT / UPDATE / DELETE のコストはインデックス数に概ね比例して増えます。さらにディスク容量も消費し、バッファプールを圧迫します。

目安:

  • 1 テーブルにつきインデックスは5〜7 本程度までを目処(ワークロード次第)
  • 同じ列の組で複数のインデックスを作らない(先頭が同じ複合と単独は重複)
  • (a, b) の複合インデックスがあれば (a) 単独は不要

使われていないインデックスを探して消す

RDBMS は「インデックスが使われた回数」を統計情報として持っています。定期的に確認し、使われていないインデックスは積極的に削除しましょう。残しておくと書き込みコストだけ払い続けることになります。

sql
-- PostgreSQL: 使用回数 0 のインデックス
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;

-- MySQL 8.0: 使われていないインデックス
SELECT * FROM sys.schema_unused_indexes;

実行計画で答え合わせする

「インデックスが使われているはず」は思い込みになりがちです。EXPLAIN(PostgreSQL なら EXPLAIN ANALYZE)で実際の実行計画を確認するクセを付けましょう。

代表的な読み方:

  • Seq Scan (PostgreSQL) / ALL (MySQL) → フルスキャン。インデックスが使われていない
  • Index Scan / ref, range → インデックスを使った検索
  • Index Only Scan / Using index → カバリング成功(テーブル本体を読んでいない)
  • Bitmap Index Scan → 複数条件を組み合わせてインデックスを利用
sql
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE user_id = 42 AND created_at >= '2026-01-01';

-- 出力例 (PostgreSQL)
-- Index Scan using idx_orders_user_created on orders
--   Index Cond: (user_id = 42 AND created_at >= '2026-01-01')
--   (actual time=0.018..0.035 rows=12)