はじめに
こんな経験ありませんか?
この記事では、
- 実際に発生した インデックス設計の失敗例
- なぜダメだったのか
- どう直して、どう判断するようになったか
を、実務で利用できる形でまとめます。
この記事で分かること
- インデックスを張る優先順位
- 張るべき / 張らないべきの判断基準
- 既存インデックスを外すときの考え方
「とりあえずのインデックス」を卒業したい人向けです。
実体験①:とりあえず張って本番で怒られた話
❌ ダメな例
「一覧が遅い」と言われて、深く考えずにこうしました。
add_index :reservations, :status
なぜダメだったか
-
statusは値の種類が少ない(低選択度) - 結果として ほぼ全件スキャン
- UPDATE 時にインデックス更新コストだけ増えた
👉 SELECTは速くならず、UPDATEだけ遅くなった
⭕ 改善例
実際に遅かったクエリはこれでした。
SELECT *
FROM reservations
WHERE customer_id = ?
ORDER BY created_at DESC
LIMIT 20;
なので張るべきだったのは:
add_index :reservations, [:customer_id, :created_at]
結果
- 一覧表示:数秒 → 数十ms
- 更新性能への影響も最小限
実体験②:JOINが遅いのに気づかなかった話
❌ ダメな例
外部キーにインデックスが無いまま JOIN。
SELECT *
FROM order_items
JOIN orders ON orders.id = order_items.order_id;
なぜダメか
- JOIN条件は WHERE と同じくらい重要
- データ量が増えた瞬間に爆死する
- 本番データで初めて遅さが顕在化
⭕ 改善例
add_index :order_items, :order_id
学び
👉 外部キーは「必要になってから」では遅い
実体験③:インデックスを張りすぎて保守不能になった話
❌ ダメな例
- 同じようなインデックスが大量に存在
- 誰も意図を説明できない
INDEX(a)
INDEX(a, b)
INDEX(a, c)
なぜダメか
- 書き込み性能が劣化
- インデックスの意図が不明
- 削除も怖くて触れない
⭕ 改善例
- 実際に使われているクエリを洗い出し
- EXPLAINで使われていないインデックスを削除
👉 「あるから使われる」わけではない
判断基準まとめ(保存用)
インデックスを張るべき判断基準
- WHEREで高頻度に使われる
- JOINに使われる
- ORDER BY + LIMIT がセット
- 本番で遅いクエリが確認されている
インデックスを張らない判断基準
- boolean / status など低選択度カラム
- 更新頻度が極端に高い
- 想定だけで存在するクエリ
インデックスを外す判断基準(棚卸し)
- EXPLAINで使われていない
- 対応するクエリが存在しない
- 複合インデックスの部分重複
- 書き込み性能を明確に悪化させている
迷ったらこの順で考える
- どのクエリを速くしたい?
- WHERE / JOIN / ORDER BY は?
- 選択度は高い?
- 書き込みへの影響は許容できる?
まとめ
- インデックスは「速くする魔法」ではない
- クエリありき
- データ量ありき
- 定期的な棚卸し前提
「とりあえず張る」より
「なぜ張るか説明できる」設計を。
