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 Costは0.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 Costは4.37、Total Costは20.50になった。- インデックスのおかげで操作が効率的になった。
ビジュアルで理解:Seq ScanとIndex Scanの違い
分かりやすいように、比較表を作ってみたよ:
| 操作 | 説明 | 例 |
|---|---|---|
| Seq Scan | テーブル全体を読む | 全行をフルスキャン |
| Index Scan | インデックスを使う | インデックス経由で素早く行を取得 |
落とし穴とよくあるミス
実行プランのパラメータを使うときは、ちょっとしたトラップもある。例えば、costが低いからといって必ずしも実行が速いとは限らない。データベースの統計情報が古い(例えば大量更新の後とか)と、プランが正確じゃなくなることもある。ANALYZEコマンドで統計を更新しよう。詳しくは次の講義で説明するね。
インデックスは必要なところで使おう。でもインデックスの作りすぎはNG:ディスク容量を食うし、書き込みも遅くなるから注意!
GO TO FULL VERSION