PostgreSQLのインデックス設計——EXPLAINで遅いクエリを改善する
はじめに
「アプリのレスポンスが遅い」という問題の原因はよくDBのクエリにあります。PostgreSQLでは EXPLAIN ANALYZE を使うことでクエリの実行計画を確認でき、どこがボトルネックになっているか把握できます。
サンプルテーブル
| |
EXPLAIN ANALYZEの読み方
| |
インデックスがない状態での出力例:
Seq Scan on orders (cost=0.00..24053.00 rows=100 width=48)
(actual time=0.028..182.451 rows=98 loops=1)
Filter: (user_id = 42)
Rows Removed by Filter: 999902
Planning Time: 0.189 ms
Execution Time: 182.498 ms
Seq Scan(シーケンシャルスキャン)は全行を読んでいる状態です。Rows Removed by Filter: 999902 が示すとおり、100万行をすべてスキャンして98行だけ返しています。
インデックスの追加
| |
再度 EXPLAIN ANALYZE を実行:
Index Scan using idx_orders_user_id on orders
(cost=0.42..432.43 rows=100 width=48)
(actual time=0.042..0.681 rows=98 loops=1)
Index Cond: (user_id = 42)
Planning Time: 0.312 ms
Execution Time: 0.714 ms
182ms → 0.7ms に改善されました。Index Scan に変わったことで、対象行だけを効率よく読めています。
複合インデックス
複数列を組み合わせた検索が多い場合は複合インデックスが有効です。
| |
複合インデックスは左端の列から順番にしか使えない点に注意します。user_id と status の複合インデックスは WHERE user_id = 42 にも効きますが、WHERE status = 'completed' だけには効きません。
部分インデックス(Partial Index)
特定の条件のレコードだけを対象にしたインデックスです。
| |
status = 'pending' の件数が全体の一部であれば、通常インデックスよりサイズが小さく、更新時のオーバーヘッドも少なくなります。
インデックスが使われないケース
インデックスを作っても使われないことがあります。代表的なケースを押さえておきます。
関数をWHEREに使っている
| |
NULLとの比較
IS NULL や IS NOT NULL に対しては通常インデックスは効きません(部分インデックスで対応可能)。
テーブルが小さい
数千件以下のテーブルではプランナーがSeq Scanの方が速いと判断することがあります。
インデックスの管理
不要なインデックスは更新・挿入時のオーバーヘッドになるため、定期的に使われていないものを確認して削除します。
| |
まとめ
| 種類 | 用途 |
|---|---|
| 単一インデックス | 1列での絞り込みが多い場合 |
| 複合インデックス | 複数列の組み合わせ検索 |
| 部分インデックス | 特定条件のレコードのみが対象 |
EXPLAIN ANALYZE でSeq Scanが出たら、まずインデックスが存在するか確認してください。存在しても使われていない場合はクエリの書き方に問題があることが多いです。インデックスは銀の弾丸ではなく、多すぎると書き込みが遅くなるため、使われているものだけを残すことが重要です。