「EXPLAIN」…それ、教科書に載ってなかったんですが?
SQLの勉強を始めた頃、「SELECT」「WHERE」「JOIN」…と少しずつ文法を覚えて、「あ、なんかそれっぽいクエリ書けるようになってきたかも!」とワクワクしていました。
ところがある日、実務で先輩が一言。
「ちょっとEXPLAINしてみて」
……EXPLAIN? あれ、そんな単語、基本文法の本に載ってたっけ……?
なんなら「EXPLAIN自体をEXPLAINしてくれ」と思ったのは、きっと私だけじゃないはずです。
実はこのEXPLAIN、“SQLが裏側でどんな処理をしているか”を教えてくれる超重要な道具なんです。
でも初見では謎の記号と数字だらけで、「理解しようとする努力が遅延の原因では?」と思いたくなる見た目。私も最初は完全に脳がフリーズしました。
でも安心してください。このEXPLAIN、読み方のコツさえわかれば“SQLが何に時間をかけているか”がスルッと見えるようになるんです。
この記事では、
- 「EXPLAINってそもそも何?」
- 「どうやって使うの?」
- 「どこを見れば“遅い理由”がわかるの?」
といった疑問に、やさしく&実例たっぷりでお答えしていきます。
読み終わる頃には、あなたのクエリ改善ライフが一歩先に進んでいるかもしれません!
そのSQL、なぜ遅いのか知っていますか?
SQLを書くとき、まず目指すのは「正しく動くこと」。
意図したデータが取得できたら「よし、完成!」と達成感に浸っていた時期が、私にもありました。
でもある日、ちょっとデータ量の多いテーブルに対してクエリを投げたときのこと。
画面はフリーズ寸前、タイムアウト目前、 “SQLは正しかったはずなのに、なぜか結果が返ってこない…”という事態に直面しました。
勘で書き換える → 効果があるかは運まかせ?
そんなとき人はどうするかというと…
- SELECT * をやめてみる(とりあえず)
- インデックス貼った気がする(たぶん)
- とりあえずWHERE句を増やしてみる(願いを込めて)
…といった“やみくも調整タイム”に突入しがちです。
しかし、それでたまたま速くなっても、「なぜ改善したのか」が説明できなければ再現できないんですよね。
実行計画とは、SQLの“動き方マニュアル”である
そこで登場するのが EXPLAIN です。
EXPLAINを使えば、SQLが裏側でどうやってデータを取得しようとしているか(処理手順・実行ルート)を確認することができます。
たとえば…
- テーブル全体をなめてる(=Seq Scanになってる)
- 結合の順番が非効率(Nested Loopで爆発してる)
- インデックスが使われてない(効いてると思ってた…)
など、“処理に時間がかかる正体”が見えてくるようになります。
「速さ」を感覚ではなく“根拠”で語れるようになる
EXPLAINは、いわば「SQLに対する診断書」。
症状が出ている箇所・ボトルネックの原因・どこを手術(改善)すれば良さそうか、という示唆をくれるツールです。
EXPLAINを読めるようになると、こんな力が身につきます:
- なぜそのSQLは遅いのか、論理的に説明できる
- 改善案に根拠を持てる → チームレビューで説得力がUP
- 複雑なクエリも“構造と目的”が見えてきて怖くなくなる
「とりあえず書いて、動いたら終わり」というスタンスから、 「なぜ速くなるのかを理解して改善する」 という一歩先のスキルへ。
EXPLAINは、その入口に立たせてくれる強力な味方なんです。
EXPLAINってなに?まずはざっくり概念を理解しよう
SQLを実行すると、パッと結果が返ってくる──
でもその裏側で「どうやってその結果を取得したか」は、普段見えません。
そこで登場するのが EXPLAIN。
これは一言でいうと、SQLが“どうやってデータを取りに行っているか”を教えてくれるツールです。
SQLの“頭の中”をのぞき見るツール
たとえば、あなたが「冷蔵庫の中からプリンを探す」場面を想像してください。
- フルスキャン:冷蔵庫の中身を上から全部出して一個ずつ確認する(時間かかる)
- インデックス活用:扉のメモに「プリン→上段左」と書いてある(すぐ取れる)
実はSQLも同じで、データを取る手順がまるで“人間の動き方”のようにプラン化されているんです。
EXPLAIN はその「プラン(計画)」を可視化する機能、つまり実行計画(Execution Plan)を出力してくれます。
何が分かるの?EXPLAINで見えること一覧
EXPLAINを使うと、以下のような情報が分かります:
項目 | 意味 | たとえば… |
---|---|---|
スキャン方法 | テーブル全体を読む?インデックスを使う? | Seq Scan / Index Scan / Bitmap Heap Scan |
処理順序 | JOINはどの順で行われる? | Nested Loop / Merge Join / Hash Join |
見積もり行数 | この処理で何件のデータを扱う予定? | rows=100 とか出てくるあれです |
処理コスト | 各処理の“重たさ”の目安 | cost=0.00..1324.45 など |
条件式 | WHERE句やJOIN条件の具体的内容 | Filter: status = ‘active’ など |
つまり、パフォーマンスを左右する要素が、全部この中に詰まっているんです。
「最適化された実行計画」ってなに?
SQLは書いた順に処理されるわけではありません。
データベースは、SQLの内容を解析して「どう処理したら最も効率的か?」を判断し、内部で最適な処理手順=実行計画を組み立ててくれます。
たとえば…
SELECT name FROM users WHERE age > 30;
このSQLを見て、DBはこう考えます:
- users テーブルにインデックスあるかな?
- WHERE句の条件に使える?
- どのスキャン方法が速そう?
- 先にフィルターかけた方が効率いい?
この一連の「考えた内容」が、EXPLAINでそのまま見える、というわけです。
まずは“ざっくり読めればOK”から始めよう
EXPLAINの出力って、正直ちょっといかついですよね。
でも最初から全部を理解する必要はありません。まずは、
- Seq Scan か Index Scan かをチェックする
- 処理の「木構造」を上から下へ読んでみる
- rowsやcostを使って「負荷のかかる場所」を見つけてみる
──それだけでも、「なんとなく原因がわかるようになってきた!」という感覚が得られるはずです。
次の章では、いよいよ EXPLAIN の実際の使い方をステップごとに紹介していきます。
「何を入力して、どこを見るのか?」をやさしく整理しますので、安心して読み進めてくださいね!
まずはやってみよう:EXPLAINの基本的な使い方
ここまでで、「EXPLAINとはなんぞや?」というイメージはざっくり掴めたかと思います。
では実際に、どんなふうに使うのか?どうやって実行計画を見ればいいのか?をステップごとに確認していきましょう!
EXPLAINの書き方は、とってもシンプル!
まずは一番基本の形から:
EXPLAIN
SELECT * FROM users WHERE age > 30;
これで、「このクエリを実行すると、裏側ではどんな処理計画が立てられるのか」を見ることができます。
結果は以下のような形式で表示されます:
Seq Scan on users (cost=0.00..431.00 rows=10000 width=100)
Filter: (age > 30)
「Seq Scanって何?」という疑問、今は大丈夫です。
このあと順を追って説明していきますので、まずは「こんなふうに出るのか〜」という感触を持っていただければOKです!
EXPLAIN ANALYZE で“実測値”を見ることもできる
基本の EXPLAIN は“予測された計画”を表示しますが、 次のように EXPLAIN ANALYZE を使うことで、実際の実行結果と所要時間まで表示できます:
EXPLAIN ANALYZE
SELECT * FROM users WHERE age > 30;
出力にはこんな情報が追加されます:
Seq Scan on users (cost=0.00..431.00 rows=10000 width=100)
Filter: (age > 30)
Rows Removed by Filter: 20000
→ actual time=0.023..1.347 rows=9874 loops=1
actual time が実際にかかった処理時間、rows が実際に処理された行数です。
ANALYZE は便利ですがクエリを実行してしまう(=本当に走る)ので、UPDATEやDELETEなど“変更系のSQL”には注意が必要です!
どこで実行するの?
PostgreSQLなら、psql・pgAdmin・DBeaver・DataGrip など、 SQLを直接入力できるクライアントであればどこでも実行できます。
「EXPLAINを使うとき専用の開発用DBに接続して試す」という運用もおすすめです!
実行結果は“木構造”のように読むのがコツ
出力結果は1行ずつではなく、“上から下”の木構造になっています。
次のように、インデントが深くなるほど“下位の処理”を表しています
Nested Loop
→ Index Scan on orders
→ Index Scan on users
この例だと:
- 外側:orders に対してインデックススキャン
- 内側:そのたびに users をインデックススキャン
といったネストループ構造になっていることが分かります。
次章では、この出力をどう読み解くか?
たとえば「Seq Scan になってるけど、これって悪いの?」「rows や cost の数字ってなにを意味するの?」といった、見方の基本をやさしく紐解いていきます。
読み方を分解!EXPLAINの各項目をやさしく解説
EXPLAINを実行すると、なんとも“暗号っぽい出力”が返ってきます。
Seq Scan on users (cost=0.00..431.00 rows=10000 width=100)
Filter: (age > 30)
「えーと、スキャン?コスト?rows?数字たくさんあるけど、どこを見ればいいの…?」 そう思う方も多いはず。
私も最初は、コストの意味が分からず「お金…?」と一瞬誤解しました。
でも大丈夫。読み方には“コツ”があります! ここではよく出てくる主要な項目の意味と、その見方を順番に整理してみましょう。
Scan Type(スキャン方式)を見る
まず最初に目を向けたいのは、テーブルの読み取り方法です。
たとえば…
表示名 | 説明 | ざっくりイメージ |
---|---|---|
Seq Scan | 全件読み込み(フルスキャン) | ページめくって最初から全部確認 |
Index Scan | インデックスを使った効率的検索 | 辞書の索引で直接ジャンプ |
Bitmap Heap Scan | インデックスを使いつつまとめ読み | ハイライトを集めて本を読む感覚 |
ポイント: Seq Scan ばかり出ているときは、インデックスが使えていない可能性大!
cost(コスト)をざっくり読み取ろう
cost=0.00..431.00
これは「処理開始〜完了まで、どれくらい負荷がかかるか」をDBが内部的に計算した見積もり値です。
- 左:開始時のコスト
- 右:終了時のコスト(こっちが重要!)
数字が大きいほど「重たい処理」であることを意味しますが、単位は“相対的な指標”なので、他のクエリと比べるときの目安として使うのが◎です。
rows(見積もり行数)をチェックしよう
rows=10000
これは「このステップで何行ぐらい処理される想定か」という推測値です。
この数字が実際と大きくズレている場合、
- 統計情報が古い
- WHERE句がうまく効いていない
- 絞り込み条件が曖昧
などの可能性があります。
EXPLAIN ANALYZE を使えば、実際に処理された行数との違いも確認できます!
Filter や Join Type にも注目!
Filter: (age > 30)
この行は、「このステップでどういった条件がかかっているか」を表しています。
JOINの場合は以下のように表示されます
Nested Loop
→ Index Scan on orders
→ Seq Scan on users
JOINの種類は以下のように読み取れます:
Joinタイプ | 処理イメージ | 注意点 |
---|---|---|
Nested Loop | A×Bをループで突き合わせ | 小規模向き。大規模だと重くなる |
Hash Join | 片方のデータをメモリに展開して一致を探す | 中規模向き。メモリ使用に注意 |
Merge Join | 並べた上で結合する | 並び順が揃っていれば高速! |
読むときのコツまとめ
- インデントが深いほど“内側の処理”と考える
- 各ブロックごとに「何を読んで・何を条件にしているか」を確認
- 上から“処理の流れ”を追うつもりで読むとスッと理解できる
EXPLAINは“全部の項目を完璧に読む”必要はありません。
まずはScan方式・cost・rows・Filterあたりに注目するだけでも、SQLのクセや改善ポイントが見えてきます!
次章では、よくある「重たいSQLパターン」にEXPLAINでツッコミを入れながら、改善の糸口を見つける方法を紹介します。
あなたのSQLにも“改善ポイント”が隠れているかも?
EXPLAINで見えてくる「重たいSQLあるある」とその改善ポイント
「なんか遅いんだよな…このSQL」 そんな経験、きっと誰しもあるはずです。
私も昔、「SELECTしただけなのに、なんで5秒もかかるの…?」と悩み、EXPLAINを恐る恐る実行してみた記憶があります。
すると出てきたのは、Seq Scan。Nested Loop。コスト1000超え。
見た瞬間、思わず閉じました。
でも、EXPLAINは正直です。 出力された実行計画を読むことで、“どこでどれだけ無駄な処理が発生しているか”が手に取るように分かるようになります。
この章では、よくある重たいSQLのパターンをもとに、EXPLAINでどこを見れば改善ポイントがわかるのかをご紹介します。
フルスキャン:Seq Scanばっかり出てる問題
EXPLAINでこう見える:
Seq Scan on users (cost=0.00..987.00 rows=10000 width=100)
Filter: (LOWER(email) = 'taro@example.com')
問題のポイント:
- WHERE句に関数を使っていてインデックスが効いていない
- インデックスが存在していても、構文によって“殺されて”しまう
改善策:
- LOWER(email) ではなく、事前に小文字で保存して email = ‘…’ 形式で検索
- PostgreSQLなら、関数インデックスを活用する手も!
ネストループ地獄:Nested Loop祭り
EXPLAINでこう見える:
Nested Loop
→ Seq Scan on orders
→ Index Scan on users
問題のポイント:
- 結合元(外側)が大きく、内側の検索が何千回も繰り返されて爆発的に遅くなる
- 結合順序が最適じゃない(小さいテーブルを先にJOINしたほうが効率的な場合も)
改善策:
- 先にフィルターされた少量のデータとJOINすることでループ回数を減らす
- Hash Join や Merge Join への切り替えが起こるようにJOINの条件を見直す
- 実行プランの調整ヒントとして EXPLAIN (ANALYZE, VERBOSE) を使うのも効果的!
想定より多すぎる rows:見積もりミスに注意
EXPLAINでこう見える:
Index Scan on users (rows=100 expected, 10000 actual)
(これは EXPLAIN ANALYZE を使ったときに出る情報)
問題のポイント:
- データベース側が「軽そう」と判断してインデックスを選んだが、実際は想定以上の件数を処理してしまった
- 統計情報が古い、あるいは絞り込み条件があいまい
改善策:
- ANALYZE を実行して統計情報を最新に保つ(PostgreSQLでは自動/手動可)
- 絞り込み条件に明確な比較や範囲指定を意識して、行数の推定精度を上げる
結合してるけど使ってないテーブル、ありませんか?
EXPLAINでこう見える:
Hash Join
→ Seq Scan on campaigns
→ Seq Scan on orders
でも、最終的に SELECT されているカラムに campaigns の内容は一切登場していない…
問題のポイント:
- 無意味なJOINでテーブルを増やし、処理量が増えているだけ
- 結果として処理も遅くなり、読みづらさも倍増
改善策:
- 本当に必要なJOINか?クエリの目的に合っているか?を再確認
- 取得カラムと結合条件を照らし合わせて「使われていないJOIN」は除外
まとめ:EXPLAINは“気づく力”をくれる
EXPLAINの出力をじーっと見ていると、
- 「あ、ここでSeq Scanしてるのはインデックスが効いてないからかも」
- 「このNested Loopはデータ量が多いから重そうだな」
- 「rows の推定ずれてるな、ANALYZEいるかも?」
──と、改善のヒントに自然と気づけるようになってきます。それはまるで、「クエリの声を聞く」ような感覚。
次の章では、そんな“悪い例の発見”を踏まえて、実際にBefore / After形式でクエリをリファクタリングしていく実践編に入っていきます!
ここまで読んだあなたなら、「直せそう!」と思える場面がきっと見つかるはずです。
実践編:重たいSQLを読みやすく&速くリファクタリングしてみよう
「このSQL、動くんだけどなんか遅い」 「レビューで『EXPLAIN見た?』って言われたけど、何が悪いのか分からない…」
そんな“あるある”な状況に遭遇したとき、役立つのがBefore → Afterの思考です。
この章では、ありがちな“改善の余地ありSQL”を題材に、EXPLAINの出力や修正ポイントを具体的に紹介します!
ケース①:インデックスが効かないWHERE句
Before
SELECT * FROM users WHERE LOWER(email) = 'taro@example.com';
EXPLAIN出力(一部)
Seq Scan on users
Filter: (lower(email) = 'taro@example.com')
問題点:
- LOWER(email) によってインデックスが無効化
- 全件スキャン(Seq Scan)が発生
After(構文を見直す)
SELECT * FROM users WHERE email = 'taro@example.com';
※ email カラムを事前に小文字で保存する運用へ変更
EXPLAIN出力:
Index Scan using idx_users_email on users
- インデックスが効いて検索が高速化!
ケース②:SELECT * のままJOIN地獄
Before
SELECT *
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 'shipped';
問題点:
- SELECT * によって不要なカラムまで取得
- users テーブルのLEFT JOINが実はINNERで良かった
After(目的ベースで修正)
SELECT o.id, o.total_price, u.name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'shipped';
- 必要なカラムだけ選ぶことでネットワーク負荷も軽減
- JOINの種類を最小限にすることで実行プランも改善!
ケース③:サブクエリネストが深すぎて混乱
Before
SELECT user_id, total
FROM (
SELECT user_id, SUM(price) AS total
FROM (
SELECT user_id, price FROM orders WHERE status = 'completed'
) t
GROUP BY user_id
) result;
After(CTEで分けてスッキリ)
WITH completed_orders AS (
SELECT user_id, price FROM orders WHERE status = 'completed'
),
user_totals AS (
SELECT user_id, SUM(price) AS total
FROM completed_orders
GROUP BY user_id
)
SELECT user_id, total FROM user_totals;
- ネストを避け、処理の流れが読みやすくなる
- 後からの修正も楽&レビューもスムーズ!
まとめ:リファクタリングは“整える快感”
この章で紹介した修正例は、すべて「ちょっと気をつけるだけ」で改善できるものばかりです。
EXPLAINで原因に気づき、 構文を整えて“読みやすさと速さ”を両立する。
このプロセスを通じて、SQLが「動くだけ」から「気持ちよく動く」ものに変わっていくのを、ぜひ体感してみてください。
次章では、EXPLAINをクエリ改善に活かすための“日常で使えるテクニック”をお届けします!
ここまでで「読めそうかも」と思えた方は、次でさらに「使いこなせそうかも」に変わっていくはずです!
EXPLAINを味方につけて、SQLと仲良くなる日常を
EXPLAINを読めるようになってくると、SQLがちょっと違って見えてきます。
たとえば「このJOINはどう処理されるんだろう?」とか、「このWHERE句、期待通りに効いてるかな?」と気になって、つい EXPLAIN を覗きたくなる。
それって、SQLを“ただ動かす”から“一緒に会話する”感覚へ進んできた証拠です。
この章では、そんなあなたに向けて「EXPLAINを日常の中でどう使いこなしていくか」のヒントをご紹介します。
書いたらすぐEXPLAIN、それが習慣に
SQLを書いたら、実行ボタンを押す前に EXPLAIN を入れてみましょう。
EXPLAIN
SELECT ...;
- フルスキャンしてる?
- rowsやcostの数字、大きすぎない?
- JOINやWHERE句の条件、思った通りになってる?
クエリを“走らせる前”に自分でレビューするつもりでチェックする習慣をつけると、無駄な処理や重たい構文に早めに気づけるようになります。
時々は EXPLAIN ANALYZE で“実測確認”もしてみる
「このクエリ、重くないと思ったんだけどな〜」 実はそういう時に限って裏でネストループ地獄だったりします。
そんな時こそ、EXPLAIN ANALYZE の出番です
EXPLAIN ANALYZE
SELECT ...;
実際の処理時間・行数を含む出力で、“予想と現実のギャップ” に気づけます。
ときどき実行しておくことで、DBの統計情報のズレや思い込みに基づいた構文の危うさも見えてきますよ。
「WHERE効いてない」問題に早めに気づく
個人的に EXPLAIN が一番ありがたかったのは、WHERE句が期待通りに効いていないことに気づけたときです。
WHERE TO_CHAR(created_at, 'YYYY-MM-DD') = '2024-05-01'
この一見よさげな書き方、実は インデックスを殺してしまう落とし穴。
EXPLAINで Seq Scan が出て「えっ…?」となり、初めて気づくパターンです。
→ 対処法は「比較対象に関数をかけない」か、「関数インデックスを使う」など
コードレビューでもEXPLAINを添えてみよう
チームで開発していると、SQLのレビューって「動くかどうか」以上に、「パフォーマンス面でも問題ないか?」が重要だったりしますよね。
そのとき、「このクエリ、Index Scan 使われてます」 「Nested LoopになってたのでJOIN順を整理しました」
──といった風に、EXPLAIN結果を根拠にしてコードを説明できるとレビューが格段にスムーズになります。
SQLとの“対話ツール”として育てていく
最初のうちは「読むのに時間がかかる」「結果をどう解釈していいか分からない」ことも多いと思います。 でも、それで大丈夫です。
1週間に1回でも、1日1回でも、 “何か怪しいSQL”を見つけたら、ふらっと EXPLAIN を試してみてください。
それだけで、あなたのSQLは少しずつ、より軽く、よりやさしく、より美しくなっていきます。
まとめ:EXPLAINは“SQLの気持ちを可視化する”ツール
クエリが詰まっていたら、「どこが苦しかったの?」と聞くように 重たい処理が混じっていたら、「どこで無理してる?」と尋ねるように
そんな風に、EXPLAINはSQLと会話するための翻訳ツールになってくれます。
書いたSQLの“その先”に目を向けたくなったとき、 EXPLAINはあなたのそばで、静かに教えてくれるはずです。
おわりに:EXPLAINを通して見えてきた、SQLとの向き合い方
この記事をここまで読んでくださったあなたは、きっと「SQLを、ただ動かすだけで終わらせたくない」と感じている方だと思います。
そして今、あなたにはEXPLAINという相棒がいます。
最初は見慣れない英語と数字に圧倒されたかもしれません。
でも、SQLの“動き方”を眺める目が少しずつ育ってきたのではないでしょうか。
遅かった理由に「気づける」ようになると、自信がつく
クエリが遅い → 書き換える → 少し速くなった気がする
以前は、そんな“祈るような改善”しかできなかった私も、今では
- Seq Scan か…関数が原因かな?
- このNested Loop、件数多いからJOIN順変えてみようかな
といった“見立てと試行”ができるようになりました。
たとえ完璧な改善じゃなくても、原因に気づけること自体が大きな成長なんですよね。
読みやすいSQLは、チームにも未来の自分にもやさしい
EXPLAINの活用を通じて、私は「読まれるSQL」を意識するようになりました。
- 結果が出ればOK → 処理の道筋が分かるクエリへ
- 気合と根性でJOIN → 読んだだけで意図が伝わる構造へ
レビューや引き継ぎでも「このSQL、整ってて助かる」と言われるようになり、 “SQLを書くことが、少し誇らしく”感じられるようになりました。
EXPLAINを「学びの相棒」にする
この先、もっと複雑なクエリや大規模なデータに出会ったとき、迷うこともあるかもしれません。
でもEXPLAINがあれば、「なぜ遅いのか」「何が起きているのか」という道しるべが、あなたの手の中にあります。
- 「なんとなく遅い」じゃなく、数字と構造で読み解く
- 「とりあえず」で直すのではなく、意図をもって改善する
そんな風にSQLに向き合えるようになると、パフォーマンス改善そのものが“楽しく”なってくるはずです。
最後に
EXPLAINの出力はちょっと難しいけど、ちゃんと読もうとすれば答えてくれる。
SQLは、構文だけじゃなく“関係性”も育てていける言語なのだと、私は思っています。
どうかあなたのクエリライフが、 速く、軽く、やさしく、誇らしいものになりますように。
またどこかで、一緒にEXPLAINを眺める日がきたらうれしいです!

EXPLAINという言葉を初めて見たとき「これは何の呪文?」と戸惑った、ちょっと前の自分に向けて書いたつもりでした。でも、今こうして読んでくださっている“あなた”のように、 「もっと意味のあるSQLを書きたい」 「ただ動けばいい、じゃなくて、きちんと向き合いたい」 そんな思いを持つ方に届いていたら、それが何より嬉しいです。
私は今でも、SQLが遅くてモヤモヤすることがあります。 でも、EXPLAINを読むたびに「ああ、こう処理されてたのか」と気づけたり、 小さな改善でスッと速くなる感触にワクワクしたり── SQLとの距離がちょっとずつ近づいていく感じが、すごく好きなんです。
これを読んだあなたの中にも、「ちょっとEXPLAIN見てみようかな」という気持ちが芽生えたなら、きっとSQLとの付き合い方が少し変わってくるはずです。
コメント