例えばアプリを開発してて、あるクエリが突然DBで一番重くなっちゃったとする。アプリが遅くなったり、エラーが出たりし始める。そんな時に登場するのがEXPLAIN。どこで問題が起きてるのかを教えてくれる。EXPLAINでクエリを分析して最適化すれば、リソース節約できるし、時間も短縮できるし、ユーザーの体験も良くなるよ。
EXPLAINは、PostgreSQLの中身を覗き見できるツール。DBがクエリをどう実行しようとしてるか見せてくれる。インデックスが使われるのか、全件スキャン(フルテーブルスキャン)になるのか、オプティマイザがどんな手順で何をするのか、中間結果がどれくらい大きいのか、全部わかる。
つまり、EXPLAINを使えばクエリ実行時に何が起きるか予想できる。「重い」クエリなのか、何行処理されるのか、どんなリソースが使われるのか。クエリが急に遅くなった時、「なぜ?」を調べるのに必須のツールだよ。
EXPLAINは暗闇の中の懐中電灯みたいなもの。中で何が起きてるか、どこで問題が起きてるか、ちゃんと見えるようになる。
EXPLAINコマンドの構文
まずはEXPLAINコマンドの基本構文を見てみよう:
EXPLAIN あなたの_SQL_クエリ;
クエリ例:
EXPLAIN SELECT * FROM students WHERE age > 20;
このコマンドを実行しても、PostgreSQLはクエリ自体は実行しない。その代わり、実行プランを見せてくれる。建設前の設計図みたいなもので、「壊す前に何が行われるか」確認できるのが便利。
出力例:
Seq Scan on students (cost=0.00..35.00 rows=7 width=37)
Filter: (age > 20)
この出力、最初はちょっと怖いかもだけど大丈夫。これから主要なパーツを解説するよ。
基本的な実行プランの読み方
さっきの結果を分解してみよう:
Seq Scan on students — これはPostgreSQLが
studentsテーブルを全件スキャン(シーケンシャルスキャン)するって意味。必ずしも悪いわけじゃないけど、大きなテーブルだとSeq Scanは遅くなりがち。cost=0.00..35.00 — これは操作のコスト見積もり:
Startup Cost: 操作開始時のコスト(ここでは0.00)。Total Cost: 操作完了までの合計コスト(ここでは35.00)。
rows=7 — PostgreSQLは
age > 20の条件で7行返すと予想してる。これを「カーディナリティ(cardinality)」って呼ぶ。変な値が出てたら、テーブルの統計情報が古いかも。width=37 — 1行あたりの平均バイト数。
Filter: (age > 20) — PostgreSQLが各行にこのフィルタを適用するってこと。
こんな感じで、EXPLAINの出力からPostgreSQLの戦略や予想が見えてくる。この情報を使って最適化できるよ。
EXPLAINコマンドのオプション
基本のEXPLAIN出力も便利だけど、次のオプションでさらに詳しくできる:
ANALYZE
このオプションを付けると、PostgreSQLは実際にクエリを実行して、実データも出してくれる。例:
EXPLAIN ANALYZE SELECT * FROM students WHERE age > 20;
これで、PostgreSQLの予想と実際の実行結果を比べて、どれくらい合ってるか確認できる。
VERBOSE
もっと細かい情報が見たい時に使う。例:
EXPLAIN VERBOSE SELECT * FROM students WHERE age > 20;
BUFFERS
クエリ実行時のバッファ(メモリ)使用状況を表示。ANALYZEと一緒に使う:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM students WHERE age > 20;
COSTS
コスト情報(cost)を表示・非表示にできる:
EXPLAIN (COSTS OFF) SELECT * FROM students WHERE age > 20;
FORMAT
プラン出力をJSONやXMLなど他のフォーマットで表示できる。例:
EXPLAIN (FORMAT JSON) SELECT * FROM students WHERE age > 20;
EXPLAINの使い方例
universityデータベースにstudentsテーブルがあるとしよう。20歳より上の学生を探したい場合:
EXPLAIN SELECT * FROM students WHERE age > 20;
出力例:
Seq Scan on students (cost=0.00..35.00 rows=7 width=37)
Filter: (age > 20)
さっきも言ったけど、これはSeq Scan(全件スキャン)で、大きなテーブルだと効率悪いかも。
じゃあ、ageカラムにインデックスを作って、プランが変わるか見てみよう:
CREATE INDEX age_index ON students(age);
EXPLAIN SELECT * FROM students WHERE age > 20;
出力:
Index Scan using age_index on students (cost=0.15..4.23 rows=7 width=37)
Index Cond: (age > 20)
今度はPostgreSQLがインデックススキャン(Index Scan)を使ってる。大きなテーブルだとこっちの方が速いことが多い。
よくある質問とミス
インデックスがあるのにクエリが遅いのはなぜ?
クエリが返す行数が多すぎると、インデックス使ってもあまり意味がない場合がある。インデックス自体が古かったり、質が悪い場合もある。
EXPLAINの出力が難しすぎる時は?
まずはシンプルなクエリから始めて、実行プランのノードを一つずつ見ていこう。
テーブルの統計情報が古いかどうか知りたい時は?
ANALYZE studentsコマンドを実行しよう
ANALYZEなしでEXPLAINを使うのはいつ?
実際にクエリを実行したくない時(例えばデータを変更するクエリの場合)に使うといいよ。
GO TO FULL VERSION