CodeGym /コース /SQL SELF /クエリ実行プランの基本コンセプト: cost, rows,...

クエリ実行プランの基本コンセプト: cost, rows, width

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

SQLクエリを書くと、PostgreSQLはいきなり実行しないんだ。まずは自分の「頭脳」— クエリオプティマイザ を動かして、実行プランを作る。このプランは地図のルートみたいなもので、PostgreSQLがどんな順番でどんなアクションをすればデータをうまく取得できるかを計算してくれる。

クエリオプティマイザは、テーブルのシーケンシャルスキャン、インデックスの利用、フィルタやソートの実行など、クエリを実行するための全てのパスを評価する。できるだけリソースを使わずにクエリを実行できる方法を探してくれるんだ。つまり、実行時間とサーバーリソースのバランスを取ろうとしてるってこと。

実行プランの主要パラメータ

じゃあ、いよいよ本題 — PostgreSQLがEXPLAINコマンドの結果で見せてくれるパラメータを見ていこう。まずはシンプルな例から:

EXPLAIN
SELECT * FROM students WHERE age > 20;

こんな感じの結果が返ってくるはず:

Seq Scan on students  (cost=0.00..35.00 rows=7 width=72)
  Filter: (age > 20)

この謎の単語や数字を分解してみよう。

1. cost(実行コスト)

cost は、そのクエリを実行するのにどれくらいリソースが必要かの見積もりだよ。このパラメータは2つの部分からできてる:

  • Startup Cost: 操作を始めるためのコスト(例えばインデックスの準備とか)。
  • Total Cost: 全体の操作を終えるまでの合計コスト。

例:

cost=0.00..35.00
  • 0.00 — これが Startup Cost
  • 35.00 — これが Total Cost

costの値が低いほど、PostgreSQLにとってそのプランが好ましい。でも、costはあくまで相対的な値で、秒やミリ秒じゃなくて、PostgreSQL内部の評価基準なんだ。

2. rows(予想される行数)

rowsは、そのクエリのこの段階でPostgreSQLが返す(または処理する)と予想している行数を示してる。今回の例だと:

rows=7

つまり、PostgreSQLはage > 20のフィルタで7行返すと予想してる。このデータは、PostgreSQLがテーブルについて集めてる統計情報から取ってる。もし統計が古いと、予想が外れることもあるし、それが最適じゃないプランにつながることもある。

3. width(1行あたりのバイト数)

widthは、この段階で返される各行の平均サイズ(バイト単位)だよ。今回の例だと:

width=72

つまり、返される各行は平均で72バイトってこと。widthはカラムのデータサイズや、行IDや管理情報などのオーバーヘッドも含んでる。

これはアプリのダウンロードサイズみたいなもの。もし「重い」(widthが大きい)と、ネットが速くても(costが低くても)ロードに時間がかかるよね。

実行プランの例を分解してみよう

実際の例を見てみよう。例えば、studentsテーブルがあるとする:

CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    age INTEGER,
    major VARCHAR(50)
);

そして次のクエリを実行する:

EXPLAIN
SELECT * FROM students WHERE age > 20 AND major = 'CS';

結果はこんな感じになるかも:

Seq Scan on students  (cost=0.00..42.50 rows=3 width=164)
  Filter: ((age > 20) AND (major = 'CS'))
  • Seq Scan: PostgreSQLはstudentsテーブルをシーケンシャルスキャンしてる。つまり全行を1つずつ見てる。
  • cost=0.00..42.50: 実行コスト。Startup Cost0.00、合計コストは42.50
  • rows=3: age > 20 AND major = 'CS'のフィルタで3行返すと予想してる。
  • width=164: 各行は平均164バイト。

これでPostgreSQLがどうやって判断してるか分かるし、クエリの弱点も見つけやすくなる。例えば、costが高いならクエリが重いかも。rowsが多すぎるなら、フィルタ条件を見直した方がいいかもね。

costは実際どう動く?

ageカラムにインデックスを追加してみよう:

CREATE INDEX idx_age ON students(age);

もう一度クエリを実行:

EXPLAIN
SELECT * FROM students WHERE age > 20 AND major = 'CS';

結果が変わるかも:

Bitmap Heap Scan on students  (cost=4.37..20.50 rows=3 width=164)
  Recheck Cond: (age > 20)
  Filter: (major = 'CS')
  ->  Bitmap Index Scan on idx_age  (cost=0.00..4.37 rows=20 width=0)
        Index Cond: (age > 20)

何が変わった?

  • Seq Scanの代わりにBitmap Heap Scanになった:PostgreSQLはまずidx_ageインデックスで該当行を探してから、テーブルから取り出してる。
  • costが大幅に下がった:Startup Cost4.37Total Cost20.50になった。
  • インデックスのおかげで操作が効率的になった。

ビジュアルで理解:Seq ScanとIndex Scanの違い

分かりやすいように、比較表を作ってみたよ:

操作 説明
Seq Scan テーブル全体を読む 全行をフルスキャン
Index Scan インデックスを使う インデックス経由で素早く行を取得

落とし穴とよくあるミス

実行プランのパラメータを使うときは、ちょっとしたトラップもある。例えば、costが低いからといって必ずしも実行が速いとは限らない。データベースの統計情報が古い(例えば大量更新の後とか)と、プランが正確じゃなくなることもある。ANALYZEコマンドで統計を更新しよう。詳しくは次の講義で説明するね。

インデックスは必要なところで使おう。でもインデックスの作りすぎはNG:ディスク容量を食うし、書き込みも遅くなるから注意!

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION