PostgreSQL実行計画の読み方ガイド!EXPLAIN ANALYZEでデータベースを高速化
生徒
「データベースからデータを取り出すのが、最近なんだか遅い気がするんです。どこが悪いのか調べる方法はありますか?」
先生
「それは、データベースがどうやってデータを探しているかという『カンニングペーパー』を見てみるのが一番の近道ですよ。」
生徒
「カンニングペーパー、ですか?」
先生
「はい。PostgreSQLには『EXPLAIN ANALYZE』というコマンドがあって、それを使うと、裏側でどう動いたかを詳しく教えてくれるんです。今日はその読み方を一緒に勉強しましょう。」
1. PostgreSQLの実行計画(EXPLAIN ANALYZE)とは?
データベースに対して「このデータを持ってきて!」とお願い(SQL)を投げたとき、データベースの内部では「どの机から、どの順番で、どうやって探そうかな?」という計画を立てます。これを「実行計画(クエリプラン)」と呼びます。
例えば、大きな図書館で一冊の本を探すシーンを想像してください。一冊ずつ背表紙を全部見ていくのか、それとも図書目録を使って棚の番号を特定してから行くのか。この探し方の違いが、データの処理速度に大きく影響します。
EXPLAIN(エクスプレイン)は、その計画を表示する命令です。そして、ANALYZE(アナライズ)という言葉を付け加えると、実際にその命令を実行してみて、「どれくらい時間がかかったか」という正確な結果も教えてくれます。
2. 実際にデータを探してみよう(全件スキャン)
まずは、何の準備もしていない状態で、たくさんのユーザーの中から特定の人を探してみましょう。ここでは「users」というテーブルに、会員の情報が入っているとします。
id | name | age | city
---+-----------+-----+----------
1 | 佐藤太郎 | 25 | 東京
2 | 鈴木花子 | 19 | 大阪
3 | 高橋一郎 | 30 | 名古屋
4 | 田中愛 | 22 | 東京
5 | 伊藤健 | 45 | 福岡
6 | 渡辺美紀 | 28 | 横浜
このテーブルから「東京に住んでいる人」を探す命令に、EXPLAIN ANALYZEをつけて実行してみます。
EXPLAIN ANALYZE
SELECT *
FROM users
WHERE city = '東京';
すると、データベースは次のような結果を返してきます。
Seq Scan on users (cost=0.00..1.07 rows=2 width=102) (actual time=0.015..0.017 rows=2 loops=1)
Filter: (city = '東京'::text)
Rows Removed by Filter: 4
Planning Time: 0.082 ms
Execution Time: 0.045 ms
ここで重要な単語を解説します。
- Seq Scan(シークエンシャル・スキャン):これは「全件読み込み」のことです。名簿の最初のページから最後のページまで、一枚ずつめくって確認している状態です。データが少ないときはいいですが、100万人もいたら大変ですよね。
- cost(コスト):データベースが予想した「作業の大変さ」です。数字が大きいほど大変な作業だと予想しています。
- actual time(アクチュアル・タイム):実際にかかった時間です。単位はミリ秒(1000分の1秒)です。
- Rows Removed by Filter:条件に合わなくて捨てられたデータの数です。今回は6人中、東京以外の人4人が捨てられたことがわかります。
3. 索引(インデックス)を使った高速な探し方
次に、データが爆速で見つかるように工夫をしてみましょう。本で言えば「索引」、辞書で言えば「あいうえお順のタブ」を作るようなものです。これをデータベースの世界では「インデックス(Index)」と呼びます。
今回は、年齢(age)を使って探すことが多そうなので、年齢にインデックスを貼ってみます。インデックスを貼った後の状態でもう一度データを探してみましょう。
id | name | age | city
---+-----------+-----+----------
1 | 佐藤太郎 | 25 | 東京
2 | 鈴木花子 | 19 | 大阪
3 | 高橋一郎 | 30 | 名古屋
4 | 田中愛 | 22 | 東京
5 | 伊藤健 | 45 | 福岡
6 | 渡辺美紀 | 28 | 横浜
-- 年齢にインデックスを作成
CREATE INDEX idx_users_age ON users(age);
-- 30歳以上の人を検索
EXPLAIN ANALYZE
SELECT *
FROM users
WHERE age >= 30;
実行結果は以下のようになります。
Index Scan using idx_users_age on users (cost=0.15..8.17 rows=2 width=102) (actual time=0.012..0.014 rows=2 loops=1)
Index Cond: (age >= 30)
Planning Time: 0.150 ms
Execution Time: 0.035 ms
新しい言葉が出てきましたね。
- Index Scan(インデックス・スキャン):これが「索引を使った検索」です。目次を見て、必要なページだけをパッと開いた状態です。全件読み込み(Seq Scan)よりも格段に早くなります。
- Index Cond:インデックスを使って、どの範囲を絞り込んだかという条件が表示されています。
もし、あなたのSQLが「遅いな」と感じて、EXPLAINで調べたときに「Seq Scan」が大量に出ていたら、それは「名簿を全部めくっていて時間がかかっている」というサインです。適切な場所にインデックスを作ってあげることで、Index Scanに変わり、劇的に速くなる可能性があります。
4. 複数の条件を組み合わせた検索の動き
実際の現場では、もっと複雑な条件で検索することがあります。「東京に住んでいる、30歳以上の人」といった具合です。このような場合、データベースはどう動くのでしょうか。
データベースは、複数の条件があるとき、どの条件を先に処理すれば一番効率的かを考えます。例えば、「東京の人」が100万人いて「30歳以上の人」が3人しかいないなら、先に年齢で絞ったほうが早そうですよね。
id | name | age | city
---+-----------+-----+----------
1 | 佐藤太郎 | 25 | 東京
2 | 鈴木花子 | 19 | 大阪
3 | 高橋一郎 | 30 | 名古屋
4 | 田中愛 | 22 | 東京
5 | 伊藤健 | 45 | 福岡
6 | 渡辺美紀 | 28 | 横浜
EXPLAIN ANALYZE
SELECT *
FROM users
WHERE city = '東京' AND age >= 25;
この結果を見てみましょう。
Bitmap Heap Scan on users (cost=4.17..9.15 rows=1 width=102) (actual time=0.020..0.022 rows=1 loops=1)
Recheck Cond: (age >= 25)
Filter: (city = '東京'::text)
Rows Removed by Filter: 2
Heap Blocks: exact=1
-> Bitmap Index Scan on idx_users_age (cost=0.00..4.17 rows=3 width=0) (actual time=0.008..0.008 rows=3 loops=1)
Index Cond: (age >= 25)
またまた難しい言葉が出てきましたが、怖がらなくて大丈夫です。
- Bitmap Index Scan(ビットマップ・インデックス・スキャン):これは「あてはまるデータの場所(住所)」をまずメモ帳にバーっと書き出す作業です。
- Bitmap Heap Scan:メモした住所を元に、実際のテーブル(データが保存されている本体)へデータを取りに行く作業です。
- Recheck Cond / Filter:取りに行った後で、「本当に東京の人かな?」と再確認(フィルター)している様子がわかります。
このように、実行計画を読むことで「今は年齢の索引は使ってくれているけど、住所の確認で時間が取られているんだな」といった、細かい原因究明ができるようになります。パフォーマンスチューニング(性能を上げること)の第一歩は、この健康診断の結果を正しく読み取ることなのです。
5. コストと実行時間の関係に注意しよう
最後に、初心者の方がよく陥る罠についてお話しします。それは「cost(コスト)」の値だけを見て一喜一憂してしまうことです。
コストはあくまでデータベースの「予測値」です。例えるなら、料理を作る前に「だいたい15分くらいでできるかな?」と予想しているようなものです。でも実際に作ってみたら、火力が強くて10分でできたり、材料を切るのに手間取って20分かかったりしますよね。これと同じことがデータベースでも起こります。
そのため、必ずANALYZEをつけて、actual time(実際にかかった時間)を確認するようにしましょう。予測と結果が大きくズレているときは、データベースが持っている統計情報(データの分布などの知識)が古くなっている可能性があります。
「ANALYZEコマンド(実行計画の方ではなく、統計情報の更新)」を実行して、データベースの知識を最新にしてあげると、もっと賢い実行計画を立ててくれるようになることもあります。データベースは生き物のように、データが増えるたびに性格や動き方が変わっていくものなのです。