カテゴリ: データベース 更新日: 2026/01/29

PostgreSQL実行計画の読み方ガイド!EXPLAIN ANALYZEでデータベースを高速化

PostgreSQLの実行計画(EXPLAIN ANALYZE)の読み方を解説
PostgreSQLの実行計画(EXPLAIN ANALYZE)の読み方を解説

先生と生徒の会話形式で理解しよう

生徒

「データベースからデータを取り出すのが、最近なんだか遅い気がするんです。どこが悪いのか調べる方法はありますか?」

先生

「それは、データベースがどうやってデータを探しているかという『カンニングペーパー』を見てみるのが一番の近道ですよ。」

生徒

「カンニングペーパー、ですか?」

先生

「はい。PostgreSQLには『EXPLAIN ANALYZE』というコマンドがあって、それを使うと、裏側でどう動いたかを詳しく教えてくれるんです。今日はその読み方を一緒に勉強しましょう。」

1. PostgreSQLの実行計画(EXPLAIN ANALYZE)とは?

1. PostgreSQLの実行計画(EXPLAIN ANALYZE)とは?
1. PostgreSQLの実行計画(EXPLAIN ANALYZE)とは?

データベースに対して「このデータを持ってきて!」とお願い(SQL)を投げたとき、データベースの内部では「どの机から、どの順番で、どうやって探そうかな?」という計画を立てます。これを「実行計画(クエリプラン)」と呼びます。

例えば、大きな図書館で一冊の本を探すシーンを想像してください。一冊ずつ背表紙を全部見ていくのか、それとも図書目録を使って棚の番号を特定してから行くのか。この探し方の違いが、データの処理速度に大きく影響します。

EXPLAIN(エクスプレイン)は、その計画を表示する命令です。そして、ANALYZE(アナライズ)という言葉を付け加えると、実際にその命令を実行してみて、「どれくらい時間がかかったか」という正確な結果も教えてくれます。

2. 実際にデータを探してみよう(全件スキャン)

2. 実際にデータを探してみよう(全件スキャン)
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. 索引(インデックス)を使った高速な探し方

3. 索引(インデックス)を使った高速な探し方
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. 複数の条件を組み合わせた検索の動き

4. 複数の条件を組み合わせた検索の動き
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. コストと実行時間の関係に注意しよう

5. コストと実行時間の関係に注意しよう
5. コストと実行時間の関係に注意しよう

最後に、初心者の方がよく陥る罠についてお話しします。それは「cost(コスト)」の値だけを見て一喜一憂してしまうことです。

コストはあくまでデータベースの「予測値」です。例えるなら、料理を作る前に「だいたい15分くらいでできるかな?」と予想しているようなものです。でも実際に作ってみたら、火力が強くて10分でできたり、材料を切るのに手間取って20分かかったりしますよね。これと同じことがデータベースでも起こります。

そのため、必ずANALYZEをつけて、actual time(実際にかかった時間)を確認するようにしましょう。予測と結果が大きくズレているときは、データベースが持っている統計情報(データの分布などの知識)が古くなっている可能性があります。

「ANALYZEコマンド(実行計画の方ではなく、統計情報の更新)」を実行して、データベースの知識を最新にしてあげると、もっと賢い実行計画を立ててくれるようになることもあります。データベースは生き物のように、データが増えるたびに性格や動き方が変わっていくものなのです。

カテゴリの一覧へ
新着記事
New1
Rails
Rails GoodJob入門!PostgreSQLベースのバックグラウンド処理を初心者向けに完全解説
New2
Ruby
Rubyで学ぶビット演算入門:&・|・^・~・<<・>>の基礎と実例
New3
Rails
RESTとRailsの関係を徹底解説!resources設計と7つの標準アクションを初心者向けにわかりやすく解説
New4
データベース
MySQLアーキテクチャ入門!初心者向けに基本構造と仕組みを徹底解説
人気記事
No.1
Java&Spring記事人気No1
Ruby
Rubyのreduceとinject入門!合計計算や集計を初心者向けに分かりやすく解説
No.2
Java&Spring記事人気No2
Ruby
Rubyの文字列エンコーディング完全ガイド!Encoding・force_encoding・encodeを初心者向け解説
No.3
Java&Spring記事人気No3
Ruby
Rubyの始め方ガイド:インストールから最初のHello Worldまで(Windows/Mac/Linux)
No.4
Java&Spring記事人気No4
データベース
PostgreSQLのWHERE句を徹底解説!初心者でもわかるSQLデータ抽出の基本
No.5
Java&Spring記事人気No5
Ruby
Rubyのfind/detect/find_indexを徹底解説!目的のデータを素早く探す方法
No.6
Java&Spring記事人気No6
Ruby
Rubyのselect/reject/filterの使い方を完全解説!初心者向けの条件抽出レシピ
No.7
Java&Spring記事人気No7
Ruby
Rubyで比較演算子を完全解説!==・===・<=>・eql? の使い分け
No.8
Java&Spring記事人気No8
データベース
PostgreSQLで順位付け!ROW_NUMBER関数の使い方を初心者向けに徹底解説