はじめに
インデックスは効きます。
でも「遅いからインデックス追加」を繰り返すと
- 書き込みが遅くなる
- VACUUMが追いつかない
- ディスクが膨れる
- どのインデックスが効いているのか分からない
という別の地獄が始まります。
この記事は、インデックスの種類を網羅するより
- どのクエリに
- どのインデックスを
- どこまで作るか
を決めるための判断チェックリストをまとめます。
インデックス追加の前にやること
まず遅いクエリを特定する
- 体感や推測で作らない
- 実際に遅いクエリをログや監視で特定する
出発点は
-
pg_stat_statementsの上位 - スロークエリログ
などです。
実行計画でボトルネックを見る
インデックスは万能ではありません。
- フルスキャンが妥当なケース
- joinの順序が悪いケース
- 集計が支配的なケース
もあります。
実行計画で見るべきポイントは
- どこで行数が爆発しているか
- フィルタが効く前に巨大なjoinになっていないか
- ソートやハッシュがメモリに乗っているか
です。
まず押さえる判断軸
読みの改善か 書きの悪化か
インデックスは
- SELECT を速くする
- INSERT UPDATE DELETE を遅くする
のトレードオフです。
- 書き込みが多いテーブルは慎重
- バッチ更新や大量INSERTがあるなら慎重
選択度が高い条件か
条件が雑すぎると、インデックスを使っても意味が薄いです。
-
statusのように値が数種類しかない -
is_deletedのような真偽値
は単体インデックスだと効かないことが多いです。
その場合は
- 複合インデックス
- 部分インデックス
が候補になります。
実務チェックリスト どのインデックスを作るか
単体インデックスでいけるか
- WHERE句でよく絞る列か
- joinキーで使う列か
複合インデックスの基本
複合は「左から順に効く」が基本です。
例
- WHERE
user_id = ?ANDcreated_at >= ?
なら
(user_id, created_at)
が第一候補です。
注意
-
(created_at, user_id)は user_id だけで検索する用途に弱い
ORDER BY と LIMIT のためのインデックス
ランキングや一覧の
- ORDER BY
- LIMIT
はインデックスで劇的に変わることがあります。
例
WHERE user_id = ? ORDER BY created_at DESC LIMIT 20
なら
(user_id, created_at DESC)
が効きやすいです。
部分インデックスを使うべき場面
論理削除やステータス絞り込みで
特定の条件に偏るなら有効です。
例
WHERE is_deleted = false
のような条件が常に付くなら
CREATE INDEX ... WHERE is_deleted = false
が効きます。
カバリングを狙うか
SELECTする列まで含めると
テーブルに行かずに済む場合があります。
ただし
- インデックスが肥大化しやすい
ので、読み頻度が高いホットパスに限定します。
増やしすぎを防ぐ運用テンプレ
インデックス台帳テンプレ
作ったら残す。
これだけで“増殖”が止まりやすいです。
- インデックス名
- 対象テーブル
- 列
- 目的のクエリ
- 効いた指標(実測)
- 追加日
- 削除検討日
削除判断のチェックリスト
- 直近の利用状況が低い
- 目的のクエリが消えた
- 同等のインデックスに包含されている
- 書き込みやVACUUMの負荷が高い
よくある落とし穴
インデックス追加で遅くなる
- 書き込みが増えてボトルネック化
- VACUUMが追いつかず膨張
特に更新が多いテーブルは、追加前に必ず影響を意識します。
結局クエリが悪いのに逃げる
- 不要に広いSELECT
- N+1
- 先に絞れないjoin
は、インデックスより先に直すべきです。
まとめ
インデックス設計は「足す技術」ではなく「足しすぎない技術」です。
遅いクエリを特定し、実行計画で支配要因を見て
目的が明確なインデックスだけを台帳付きで追加する。
この運用を入れると、長期的にパフォーマンスと保守性が安定します。