今日はPostgreSQLの実行プランノードって何なのか、どうやって読むのか、そして何かおかしい時にどう気づくかを見ていくよ。なんでデータベースがリソース食いまくるSeq Scanを選ぶのか、インデックスあるのに?って疑問も解消しよう。
PostgreSQLがクエリの実行プランを作るとき、処理を「ノード」っていう段階に分けるんだ。各ノードはデータベースサーバーがクエリを処理するための「ステップ」みたいなもの。主なノードタイプはこんな感じ:
Sequential Scan (Seq Scan)
Seq Scan、つまりシーケンシャルスキャンは、テーブルからデータを取り出す一番シンプルな方法。PostgreSQLはテーブルをまるごと1行ずつ読んで、クエリの条件に合うかチェックするよ。
Seq Scanはいつ使われる?
Seq Scanはこんな時に使われる:
- テーブルにクエリを速くするインデックスが無いとき。
- フィルタ条件がざっくりしすぎてて、インデックス使っても意味ないとき(例:全体の50%以上を取る場合)。
- PostgreSQLが「インデックスよりシーケンシャルに読んだ方が速い」と判断したとき(小さいテーブルだとよくある)。
EXPLAIN SELECT * FROM students WHERE age > 18;
結果例:
Seq Scan on students (cost=0.00..35.50 rows=10 width=50)
Filter: (age > 18)
Seq Scan on studentsに注目してね。これは「students」テーブルを全部読むよってPostgreSQLが言ってる。
Seq Scanの問題点: テーブルがでかいと、シーケンシャルスキャンはめっちゃ時間かかる。
Index Scan
Index Scanはインデックスを使ったスキャン。PostgreSQLでインデックスを作ると、テーブルの「目次」みたいなものができる。クエリがインデックスを使える場合、テーブル全体じゃなくて必要な部分だけを読みにいく。
Index Scanはいつ使われる?
- インデックスがあるカラムにフィルタ条件(
WHERE)があるとき。 =,<,>,BETWEENみたいな比較演算子を使うとき。
CREATE INDEX idx_students_age ON students(age);
EXPLAIN SELECT * FROM students WHERE age = 18;
結果例:
Index Scan using idx_students_age on students (cost=0.15..8.27 rows=1 width=50)
Index Cond: (age = 18)
ここでIndex Scan using idx_students_ageって出てるのは、PostgreSQLがidx_students_ageインデックスを使ってるってこと。全行読む代わりに、インデックスで一気にアクセスできるから速い!
Index Scanのメリット:
- 大きいテーブルでもクエリがめっちゃ速くなる。
- ディスクから読むデータ量が減る。
Index Scanの問題点:
もしクエリが返すデータが多すぎる(例えばテーブルの半分以上)、インデックス使うよりSeq Scanの方が速い場合もある。
Hash Join
Hash Joinは2つのテーブルを結合するときに使う(例:ON students.course_id = courses.id)。PostgreSQLは小さい方のテーブルでハッシュテーブルを作って、もう一方のテーブルで一致する行を探す。
Hash Joinはいつ使われる?
INNER JOINやLEFT JOINなどでテーブルを結合するとき。- PostgreSQLが他の結合方法より
Hash Joinが効率的だと判断したとき。
EXPLAIN
SELECT *
FROM students
JOIN courses ON students.course_id = courses.id;
結果例:
Hash Join (cost=25.00..50.00 rows=10 width=100)
Hash Cond: (students.course_id = courses.id)
-> Seq Scan on students (cost=0.00..20.00 rows=10 width=50)
-> Hash (cost=15.00..15.00 rows=10 width=50)
-> Seq Scan on courses (cost=0.00..15.00 rows=10 width=50)
ここではHash Joinで2つのテーブルを結合してる。PostgreSQLはまず両方のテーブルでSeq Scanをして、Hashノードでハッシュテーブルを作る。
Hash Joinのメリット:
- 中規模テーブルの結合が速い。
- 行数が多いテーブル同士の結合にも強い。
Hash Joinの問題点:
ハッシュテーブルがメモリに収まりきらないと、PostgreSQLはディスクを使うから、めっちゃ遅くなる。
実行プランの解析例
実際の例を見てみよう。
クエリ:
EXPLAIN ANALYZE
SELECT *
FROM students
JOIN courses ON students.course_id = courses.id
WHERE students.age > 18;
結果:
Hash Join (cost=35.00..75.00 rows=5 width=100) (actual time=1.00..2.50 rows=5 loops=1)
Hash Cond: (students.course_id = courses.id)
-> Seq Scan on students (cost=0.00..40.00 rows=10 width=50) (actual time=0.50..1.00 rows=7 loops=1)
Filter: (age > 18)
Rows Removed by Filter: 3
-> Hash (cost=25.00..25.00 rows=5 width=50) (actual time=0.30..0.30 rows=5 loops=1)
-> Seq Scan on courses (cost=0.00..20.00 rows=5 width=50) (actual time=0.20..0.25 rows=5 loops=1)
Planning Time: 0.50 ms
Execution Time: 3.00 ms
解釈:
Hash Join: メインノード。PostgreSQLがstudentsとcoursesを結合してる。actual time: 1.00〜2.50ms。rows=5: クエリの結果は5行。
- ネストされたノード:
Seq Scan on students:studentsテーブルを順番に読んで(age > 18)フィルタをかけてる。Rows Removed by Filter = 3: 3行が条件に合わず除外された。Hash:coursesテーブルのためにハッシュテーブルを作ってる。
ノードの比較と選び方
実行プランを読むときは、なんでPostgreSQLがその方法を選んだのかを考えるのが大事。時には自分でインデックスを追加したり、クエリを書き直したりして改善できる。アドバイスをいくつか:
- 大きいテーブルで
Seq Scanが出てたら、インデックスを検討しよう。 Hash Joinが遅いなら、PostgreSQLのメモリ設定を見直そう。EXPLAIN ANALYZEで予想値と実際の値(rowsやtime)を比べてみよう。
ここまでで、クエリ実行プランの読み方とノードの解釈の基本はバッチリ!次の講義では、よくある最適化の問題とその解決方法をやっていくよ。
GO TO FULL VERSION