Day16 前半のゴール
「“SQLがどう動いているか”を目で見て理解する感覚を持つ」
ここからは「設計とパフォーマンス」のフェーズです。
Day16 のテーマは、PostgreSQL の実行計画を見るための EXPLAIN / EXPLAIN ANALYZE。
前半のゴールはこうです。
なぜ実行計画を見る必要があるのかを、感覚レベルで理解する。EXPLAIN と EXPLAIN ANALYZE の違いを説明できる。
実行計画の“最低限ここだけは見る”ポイント(コスト・行数・インデックス利用)を掴む。
「SQLが遅い」ときに、闇雲に書き換えるのではなく、
“何がボトルネックか”を目で見て判断できるようになるのが、このDayの狙いです。
なぜ実行計画を見るのか
「“遅いSQL”の原因は、コードではなく“動き方”にあることが多い」
まず、根本の話からいきます。
SQLは「何をしたいか」を宣言する言語で、
「どうやって実行するか」は、DB側(PostgreSQL)が決めます。
同じSQLでも、
インデックスを使ってサクッと取る場合
全件をなめてから絞り込む場合
など、内部の“動き方”がまったく違うことがあります。
アプリ側から見ると「同じSQL」でも、
PostgreSQLの中では「全然違うルートで処理している」わけです。
だから、「SQLが遅い」ときに、
WHERE句をちょっといじる
JOINの順番を変える
といった表面的な修正だけをしても、
本質的なボトルネックに届かないことが多い。
そこで必要になるのが、「実行計画を見る」という行為です。
EXPLAIN の基本
「“このSQLをどう実行するつもりか”を教えてもらう」
EXPLAIN は、「このSQLを実行するとき、PostgreSQLはどういう手順で動くつもりか」を教えてくれるコマンドです。
例えば、こういうクエリがあるとします。
SELECT *
FROM order_items
WHERE user_id = 123
ORDER BY ordered_at DESC
LIMIT 10;
SQLこれに対して、こう書きます。
EXPLAIN
SELECT *
FROM order_items
WHERE user_id = 123
ORDER BY ordered_at DESC
LIMIT 10;
SQLすると、PostgreSQLは「実行計画」をテキストで返してきます。
中身はこんな感じのイメージです(実際の表示は環境によって多少違います)。
Limit (cost=0.43..15.21 rows=10 width=...)
-> Index Scan using idx_order_items_user_id_ordered_at
on order_items (cost=0.43..1200.00 rows=8000 width=...)
Index Cond: (user_id = 123)
Order By: ordered_at DESC
ここで重要なのは、「この時点では実際にはクエリを実行していない」ということです。EXPLAIN は、「こう動く予定だよ」という“予告”だけを見せてくれます。
EXPLAIN ANALYZE の基本
「“実際に動かして、その結果も見せて”もらう」
EXPLAIN ANALYZE は、EXPLAIN の“本気版”です。
EXPLAIN ANALYZE
SELECT *
FROM order_items
WHERE user_id = 123
ORDER BY ordered_at DESC
LIMIT 10;
SQLこれは、実際にクエリを実行したうえで、
どのルートで処理したか(実行計画)
どれくらい時間がかかったか
何行を読み、何行を返したか
といった“実測値”を教えてくれます。
表示イメージはこうです。
Limit (cost=0.43..15.21 rows=10 width=...)
(actual time=0.050..0.120 rows=10 loops=1)
-> Index Scan using idx_order_items_user_id_ordered_at
on order_items (cost=0.43..1200.00 rows=8000 width=...)
(actual time=0.050..0.110 rows=50 loops=1)
Index Cond: (user_id = 123)
Order By: ordered_at DESC
ここでのポイントは、
cost=... は「PostgreSQLが事前に予測したコスト」。actual time=... rows=... は「実際に動かしてみた結果」。
という違いです。
EXPLAIN だけだと「予測」しか分からない。EXPLAIN ANALYZE だと「予測と現実のズレ」まで分かる。
パフォーマンスチューニングを真面目にやるときは、
基本的に EXPLAIN ANALYZE を使います。
実行計画の“最低限ここだけは見る”3ポイント
「ノイズを捨てて、ボトルネック候補だけを拾う」
実行計画は情報量が多いので、
最初から全部理解しようとすると挫折します。
まずは、次の3つだけに絞って見てください。
どのノードが一番コストが高いか
どのノードが一番時間を食っているか(actual time)
インデックスを使っているか、それとも Seq Scan(全件走査)になっているか
さっきの例で言うと、
Index Scan using idx_order_items_user_id_ordered_at
→ インデックスを使っているので、良さそう。
もしこれが、
Seq Scan on order_items (cost=...)
Filter: (user_id = 123)
のようになっていたら、「全件走査してから user_id で絞っている」ことになります。
データ量が多いと、これが一気にボトルネックになります。
「Seq Scan なのか Index Scan なのか」を見るだけでも、
インデックス設計の問題に気づけるようになります。
例題:インデックスが効いている場合と効いていない場合
「実行計画で“インデックス設計の良し悪し”を目で見る」
同じクエリでも、インデックスの有無で実行計画が変わります。
例えば、order_items(user_id, ordered_at) にインデックスがある場合。
CREATE INDEX idx_order_items_user_id_ordered_at
ON order_items (user_id, ordered_at DESC);
SQLこの状態で EXPLAIN すると、さっきのように Index Scan が出ます。
逆に、このインデックスがない状態で EXPLAIN すると、こうなりがちです。
Limit (cost=0.00..200.00 rows=10 width=...)
-> Seq Scan on order_items (cost=0.00..20000.00 rows=10000 width=...)
Filter: (user_id = 123)
ここでの違いは、
Index Scan
→ 条件に合う行だけをインデックスから拾ってくる。
Seq Scan
→ テーブル全件をなめて、条件に合う行だけを残す。
というところです。
実行計画を見ることで、
「このクエリはインデックスをちゃんと使っているか?」
「どのカラムにインデックスを張るべきか?」
を、感覚ではなく“証拠付き”で判断できるようになります。
実行計画を見るときの“心構え”
「怖がらずに、まずは1本のクエリから」
正直に言うと、実行計画は最初はかなり怖く見えます。
でも、Day16 前半でやりたいのは、「怖さを少し剥がすこと」です。
やることはシンプルで、
よく使う1本のSELECTを決める。
それに対して EXPLAIN と EXPLAIN ANALYZE を1回ずつ打ってみる。
「Seq Scan か Index Scan か」「どこが一番時間を食っているか」だけを見る。
これだけで、「SQLがどう動いているか」を“目で見る”経験が1つ積めます。
そこから少しずつ、「JOINの実行計画」「GROUP BY の実行計画」などに広げていけばいい。
いきなり全部分かろうとしなくていいので、「まずは1本のクエリと仲良くなる」と決めてしまうのがコツです。
Day16 前半のまとめ
実行計画は、「同じSQLでも、PostgreSQLが内部でどんなルートで処理しているか」を教えてくれるもので、EXPLAIN は「予測だけ」、EXPLAIN ANALYZE は「実際に動かしてみた結果(actual time・rows)」まで見せてくれる。
最低限見るべきポイントは、「Seq Scan なのか Index Scan なのか」「どのノードのコスト・実測時間が一番大きいか」で、インデックスが効いているクエリなら Index Scan using ... が出る一方、効いていないクエリは Seq Scan on ... Filter: ... のように全件走査になっていることが多い。
Day16 前半の着地点は、「よく使う1本のクエリに対して EXPLAIN / EXPLAIN ANALYZE を打ち、SQLの“動き方”を目で見てからインデックスや設計を考える」という習慣の入口に立つこと。
