CodeGym /コース /SQL SELF /実行プランの読み方とノードの解析(`Seq Scan`、`Index Scan`、`Hash Join`)

実行プランの読み方とノードの解析(`Seq Scan`、`Index Scan`、`Hash Join`)

SQL SELF
レベル 41 , レッスン 4
使用可能

今日は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 JOINLEFT 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

解釈:

  1. Hash Join: メインノード。PostgreSQLがstudentscoursesを結合してる。
    • actual time: 1.00〜2.50ms。
    • rows=5: クエリの結果は5行。
  2. ネストされたノード:
    • Seq Scan on students: studentsテーブルを順番に読んで(age > 18)フィルタをかけてる。
    • Rows Removed by Filter = 3: 3行が条件に合わず除外された。
    • Hash: coursesテーブルのためにハッシュテーブルを作ってる。

ノードの比較と選び方

実行プランを読むときは、なんでPostgreSQLがその方法を選んだのかを考えるのが大事。時には自分でインデックスを追加したり、クエリを書き直したりして改善できる。アドバイスをいくつか:

  • 大きいテーブルでSeq Scanが出てたら、インデックスを検討しよう。
  • Hash Joinが遅いなら、PostgreSQLのメモリ設定を見直そう。
  • EXPLAIN ANALYZEで予想値と実際の値(rowstime)を比べてみよう。

ここまでで、クエリ実行プランの読み方とノードの解釈の基本はバッチリ!次の講義では、よくある最適化の問題とその解決方法をやっていくよ。

1
アンケート/クイズ
クエリ実行プラン、レベル 41、レッスン 4
使用不可
クエリ実行プラン
クエリ実行プラン
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION