Oracle SQLの実行計画(EXPLAIN PLAN)完全ガイド:読み方・ボトルネック特定・改善手順まで
この記事でできるようになること(導入:最短)
実行計画(EXPLAIN PLAN)は、SQLが「どの順番で・どの方法で」データにアクセスするかを示す“設計図”です。
SQLが遅いとき、SQL文だけ眺めても原因が見えないことがありますが、実行計画を見ると「どの表を先に読んでいるか」「どこで件数が増えているか」「どの処理が重そうか」がかなりの精度で追えるようになります。
この記事では、Oracleを前提に実行計画の取得→読み方→遅い箇所(ボトルネック)の特定→改善の当たり付けまでを、手順として再現できる形にまとめます。
また、よくある“つまずき”である「FULLが出ている=悪なの?」「Costが低いのに遅いのはなぜ?」「計画が毎回変わるのはバグ?」といった疑問にも、最後のFAQで短く答えます。
最終的には、次の状態を目指します。
- 実行計画を見て、まず疑うべき箇所(アクセス/JOIN/SORT)を自力で絞れる
- 改善案を複数出し、影響が大きい順に着手できる
- 「なぜ遅いのか」を、実行計画の根拠付きで説明できる
- 改善前後で“何が変わったか”を比較し、再発時も同じ手順で調査できる
実行計画=SQLの“動き方の設計図”
SQL文そのものが同じでも、Oracleが選ぶアクセス経路(フルスキャンかインデックスか)や結合方式(JOINのやり方)によって、処理時間は大きく変わります。
たとえば、同じWHERE句でも「索引で数十行に絞ってから結合」するのと「全件読んでから結合」するのでは、体感で桁違いになることがあります。
実行計画を読めるようになると、「どこで時間が溶けているか」を推測ではなく根拠付きで説明でき、改善の優先順位も付けやすくなります。
特に現場では、
- 変更前後で実行計画がどう変わったか
- Rows(推定行数)の増減がどこで起きたか
- JOINやSORTが“重い理由”がどこにあるか
- その改善が「他のSQLや更新処理」に与える副作用は何か
を説明できると、レビューや関係者調整がスムーズになります。
今日から見るべき3点
まずは次の3つだけ意識すると、ボトルネックの当たりが付きやすくなります。
慣れるまでは「読む順番」を固定し、毎回同じ手順で確認するのがコツです。
- アクセス経路:TABLE ACCESS FULL なのか、INDEX で絞れているのか(本当に絞れている?)
- 結合方式:NESTED LOOPS / HASH JOIN / MERGE JOIN のどれで、データ量に合っているか(外側が大きくない?)
- 並べ替え(SORT):ORDER BY や集計で大きなSORTが発生していないか(前段のRowsを減らせない?)
補足:この3点は「どれが正しいか」を当てるゲームではなく、“怪しい場所の順番”を決めるための道具です。
たとえばJOINが重そうでも、原因はその前段のFULL(絞り込み不全)だった、というのはよくあるパターンです。
EXPLAIN PLANとは(分かること・分からないこと)
EXPLAIN PLANは「OracleがそのSQLをどう実行しようとしているか」を、ツリー構造で表示する機能です。
つまり、SQLの“実行順序の見取り図”が手に入るイメージです。
ただし、ここで出てくるRowsやCostは“推定値”であり、実行時の実測値とは一致しないことがあります。
まずは“できること/できないこと”を押さえて、誤読を防ぎましょう。
ポイントは、EXPLAIN PLANを「答え」ではなく「仮説を立てるための材料」として扱うことです。
実行計画は、次のような問いに強いです。
- どの表が最初に読まれている?(入口が正しい?)
- その表はインデックスで絞れている?(想定の条件が効いている?)
- JOINやSORTは発生している?それは“必要だから”か、“前段が膨らんだから”か?
EXPLAIN PLANで分かること/分からないこと
分かることは主に次のとおりです。
- どの表を、どの順番で触るか
- フルスキャンかインデックスか(アクセスパス)
- JOIN方式や、並べ替え(SORT)・集計の有無
- Oracleが見積もった処理量(推定Rows)と相対的な重さ(Cost)
また、Predicate(条件)が表示できる場合は、次の点まで追えます。
- どの列で絞り込もうとしているか
- 結合条件がどこで適用されているか
- 条件が「アクセス条件」なのか「フィルタ条件」なのか(表示形式による)
この情報があると、「条件が効かない」系の原因に当たりを付けやすくなります。
一方で、次のことはEXPLAIN PLAN“だけ”では確定できません。
- 実行時の実測行数(実際に何行読んだか)
- I/O待ち、ロック待ち、ネットワーク待ちなど「待機イベント」
- キャッシュ状況や同時実行数の影響
- “たまたま速かった/遅かった”を分ける要因(負荷・競合・スキュー)
つまり、EXPLAIN PLANは原因の候補を絞るための地図であって、最終判断には実測や周辺状況の確認が必要になります。
実行時統計(実測)との違いと限界
EXPLAIN PLANは「実行“前”に作られる計画」です。
Oracleは統計情報(テーブル・インデックスの分布など)を元にRowsやCostを推定しますが、推定が外れることは珍しくありません。
特に、データが偏っている列や、直近でデータ量が増減した表では推定が外れやすくなります。
また、実行計画は固定ではなく、次のような要因で変わることがあります。
- 統計情報の更新(分布が変わる)
- バインド変数の値による選択度の違い
- パラメータ、ヒント、インデックスの追加/削除
- 解析対象のオブジェクト増減やデータ偏り
- 並列実行やメモリ状況など、環境設定の違い
そのため、「計画だけを見て断定する」のではなく、計画→仮説→改善→検証の流れで進めるのが安全です。
実務では、次の手順までやると再発時の対応も早くなります。
- 変更前の計画と数値(Rows/Cost)を保存
- 変更後に計画がどう変わったか比較
- 実測で改善したか(時間・I/O・待機)を確認
表記ルール(用語・表記ゆれの統一方針)
この記事では、読みやすさのため用語を次のルールで統一します。
- 実行計画:実行計画(EXPLAIN PLAN)
- オペレーション:Operation(オペレーション)
- アクセスパス:アクセス経路(アクセスパス)
- 結合方式:JOIN方式(結合方式)
- 推定行数:Rows(推定行数)
- コスト:Cost(コスト)
また、実行順の考え方は「基本は下から上(子→親)」で統一して説明します(例外はありますが、まずはこの読み順で十分に戦えます)。
実行計画の取得〜表示(これで迷わない1セット)
実行計画は「取得できる」だけでは不十分で、読みやすい形で表示して初めて意味が出ます。
特にPredicate(条件)やRows/Costが見えないと、原因の当たりが付けづらくなります。
ここでは、取得→表示→読む、を1セットとして押さえます。
方法1:EXPLAIN PLANの基本手順
もっとも基本的な流れは次のとおりです。
- 対象SQLの前に EXPLAIN PLAN FOR を付けて実行する
- 生成された計画を表示する
たとえば、次のようなイメージです(SQLは環境に合わせて調整してください)。
- EXPLAIN PLAN FOR <あなたのSQL>
- その後に表示コマンドで確認
重要なのは、EXPLAIN PLANは“実行”していないという点です。
実行による待機や実測I/Oは出ないので、まずは「経路と構造」を見る用途で使います。
- 実行計画の取得は「同じSQL」でも状況で変わることがあるため、検証時は“同じ条件(バインド値、統計、インデックス構成)”を揃える意識が大切です。
- SQLを書き換えた場合は、同等条件(同じ結果セット)になっているかも確認します。
表示:DBMS_XPLANで見やすく出す
Oracleでは、DBMS_XPLANで実行計画を整形表示するのが定番です。
SQL Developerの表示よりも「列が揃う」「Predicate(条件)を見やすく出せる」などの利点があります。
表示するときは、次を意識すると読み取りが一気に楽になります。
- Operation / Name:何をしているか、どの表/索引か
- Rows / Cost:推定処理量と相対的な重さ
- Predicate:どの条件で絞り込み(または結合)しているか
DBMS_XPLANの具体的なオプションは環境・権限・出したい項目で変わるため、まずは「Rows/Cost/Predicateが見える形式」にできているかを確認してください。
補足として、Predicateが見えると次の“地雷”を発見しやすくなります。
- 列に関数が掛かっていて索引が使えない
- 暗黙の型変換が起きていて条件が効かない
- 結合条件が想定と違う(あるいは抜けている)
- OR条件でアクセスパスが不安定になっている
方法2:SQL Developerでの確認
SQL Developerには「実行計画」ボタンがあり、GUIで計画を確認できます。
初学者には便利ですが、環境差分(表示列やレイアウト)が出やすいので、次の要点だけ押さえておくと迷いにくいです。
- ツリーを展開して子→親の流れを追う
- FULL SCAN / JOIN / SORT など「重くなりやすい操作」をまず探す
- Predicate(条件)表示がある場合は何で絞れているかを確認する
GUIは視覚的に追いやすい反面、コピーして比較しづらいことがあります。
改善前後の比較をしたい場合は、DBMS_XPLANでテキストとして残す運用が向いています。
確認時のコツ(並び順・見る列の優先度)
読み始めは、次の順で見るのがおすすめです。
- TABLE ACCESS FULL / 大きいSORT がないか
- JOIN方式 がデータ量に合っていそうか
- Rows(推定行数)が不自然に大きくないか、親子で急に膨らんでいないか
- Predicate が適切に使われているか(絞り込みや結合条件が効いているか)
慣れてきたら、次の観点も追加すると精度が上がります。
- FULLが許容される規模か(対象表の大きさ・頻度)
- 取得列が多すぎないか(I/O増大)
- Rowsの増加点が“結合”なのか“条件不全”なのか
以降の章では、この順番で“どこを見るか”を具体化していきます。
Oracle実行計画の基本構造(読み方の土台)
実行計画の読み方が安定しない原因の多くは、「ツリー構造」「親子関係」「実行順」を曖昧なままにしていることです。
ここを固めると、Operationの意味を暗記しなくても筋道立てて読めるようになります。
読むときは常に「下位の処理が材料を作り、上位の処理がそれを加工する」という発想を持つと、迷いが減ります。
ツリー構造:親Operationと子Operation
実行計画はツリーで表示され、子(下位の処理)が結果を作り、親(上位の処理)がそれを受け取って次の処理をするという関係になります。
- 子:表アクセス、インデックスアクセス、フィルタ、ソートなど
- 親:JOIN、集計、最終出力など
たとえばJOINが親にあり、その下に2つの表アクセスが子としてぶら下がる、という構造がよくあります。
さらにその表アクセスの下にインデックスアクセスがある、といった「多段構造」になることもあります。
ここで大事なのは、「親が重そう」に見えるときでも、親が重い原因が子の出力Rowsの多さだったり、子のアクセス方法が非効率だったりする点です。
親だけを叩くより、まず子に原因がないかを疑います。
実行順の考え方(基本は“下から上”)
読み方のコツは、基本は下から上(子→親)です。
- まず「どの表をどう読んでいるか」(最下層のアクセス)
- 次に「どの方式で結合しているか」(JOIN)
- 最後に「並べ替えや集計があるか」(SORT / GROUP BY)
この順で追うと、「なぜそのJOIN方式になるのか」「どこで件数が膨らんでいるのか」が説明しやすくなります。
逆に、上から読むと“結果だけ”を見てしまい、原因(前段の膨らみ)を見落としやすくなります。
補足として、JOINの下に2本の枝がある場合は、まず各枝(各表アクセス)のRowsを見て「どちらが大きいか」を把握すると、JOIN方式の妥当性が判断しやすくなります。
主要列(Id / Operation / Name / Rows / Cost)の役割
実行計画を読むうえで、最低限押さえたい列は次のとおりです。
- Id:処理の識別子(ツリーの位置)
- Operation:何をしているか(TABLE ACCESS FULL、HASH JOINなど)
- Name:対象オブジェクト(表名、索引名など)
- Rows:Oracleが見積もった出力行数(推定行数)
- Cost:Oracleが見積もった相対コスト(小さいほど良い、ではないが目安になる)
特にRowsは、後述の「推定ズレ」に気づくための重要な手がかりになります。
Rowsの増減を追うと、「どこで爆発しているか」「どこで絞れていないか」が見えます。
Costは「相対指標」なので、同じSQLの改善前後で比較する用途に向きます(別SQLとの単純比較は危険)。
Predicate(フィルタ/アクセス条件)の見方
Predicate(条件)は「どの条件で絞ったのか」「どの列で結合したのか」を示します。
ここを見ると、次の判断がしやすくなります。
- インデックスを使える条件になっているか(列に関数が掛かっていないか、型変換が起きていないか)
- 結合条件が適切か(意図しない結合・欠落している条件がないか)
実行計画で「経路が期待と違う」とき、Predicateが原因になっているケースは多いです。
特に“列に関数”や“暗黙変換”は見落としやすいので、ボトルネック候補が見えたら必ずセットで確認します。
頻出Operation早見表(意味→遅い条件→改善方向)
ここでは、現場でよく見るOperationを「意味→遅くなる条件→改善方向」の統一フォーマットで整理します。
辞書的に引けるよう、まずは表で全体像を掴んでから、必要な箇所だけ補足を読む使い方がおすすめです。
読み方のコツは「Operation名を覚える」よりも、目的(絞る/結合する/並べ替える)と、重さの理由が前段のRowsにないかをセットで考えることです。
アクセス系:FULL / INDEX RANGE / UNIQUE
アクセスパスは性能に直結します。
ただし「FULL=悪」と決めつけず、データ量・選択度・読みたい列を踏まえて判断します。
| Operation | 何をしているか | 遅くなりやすい条件 | 改善の方向性 |
|---|---|---|---|
| TABLE ACCESS FULL | 表を先頭から最後まで読む | 対象表が大きい/絞り込みが効いていない | WHERE条件見直し、適切なインデックス、不要列削減 |
| TABLE ACCESS BY INDEX ROWID | インデックスで見つけたROWIDで表を取りに行く | 取得行数が多いと往復I/Oが増える | カバリング索引、条件の選択度改善 |
| INDEX RANGE SCAN | 条件範囲に合うインデックス範囲を読む | 範囲が広すぎる/戻り行が多い | 複合索引、先頭列の見直し、条件追加 |
| INDEX UNIQUE SCAN | 一意キー等で1件(または極少)に絞る | ほぼ起きにくい(基本は速い) | 条件の等価比較を保つ、型変換回避 |
補足として、TABLE ACCESS FULLは統計上「ほとんど全件読む」なら合理的な場合もあります。
問題は本当は絞れるのに絞れていないことで、気になるときはPredicateを見て「索引で使える形」になっているかを確認します。
追加の観点として、INDEX RANGE SCANが出ていても遅い場合は、範囲が広くて結局たくさん読む、またはROWIDで表に取りに行く回数が多い、という落とし穴を疑います。
JOIN系:NESTED LOOPS / HASH JOIN / MERGE JOIN
JOIN方式は「どちらが小さいか」「結合条件が等価か」「ソート済みか」によって向き不向きが変わります。
どれが正解というより、入力Rowsと条件に合っているかが全てです。
| Operation | 何をしているか | 遅くなりやすい条件 | 改善の方向性 |
|---|---|---|---|
| NESTED LOOPS | 片方(外側)を回し、内側を繰り返し検索 | 外側が大きいのに内側検索が重い | 外側を小さくする、内側に索引、JOIN順見直し |
| HASH JOIN | ハッシュ表を作って等価結合する | 片方が大きすぎてメモリ不足/スピル | フィルタで縮める、結合順、統計見直し |
| MERGE JOIN | 両方をソートしてマージする | ソートが大きい/事前ソートが必要 | 既にソート済みを活かす、不要ORDER BY削減 |
補足として、NESTED LOOPSは「外側が小さい」ほど強く、外側が大きいと内側の検索が何万回も繰り返されて一気に遅くなります。
HASH JOINは等価結合で強い一方、入力が大きいとメモリに乗らず遅くなることがあります。
追加の観点として、JOINが重いときは「JOIN方式を変える」前に、JOIN前にどれだけ絞れるか、結合キーに適切な索引があるかを確認すると無駄な試行錯誤が減ります。
SORT・集計系:SORT / GROUP BY / WINDOW
SORTは「データ量×並べ替え」が効くので、大きいと一気に重くなります。
ソートが出たら“ソートを消す”より先に、まず“ソートに入るRowsを減らせないか”を考えると効率的です。
| Operation | 何をしているか | 遅くなりやすい条件 | 改善の方向性 |
|---|---|---|---|
| SORT ORDER BY | ORDER BYのために並べ替える | 並べ替え対象が大きい | 取得行数削減、索引で順序を満たす |
| SORT GROUP BY | GROUP BYのためにソート/集約 | 集計対象が大きい | 先に絞る、集計の粒度見直し |
| WINDOW SORT | 分析関数での並べ替え | パーティションが大きい | パーティション分割、前段の絞り込み |
追加の観点として、GROUP BYやDISTINCTが重いときは「そもそも重複が生まれるJOINになっていないか」も疑います。
集計や重複排除は“後ろで帳尻を合わせる”処理になりがちなので、前段のRows管理が特に重要です。
ボトルネックの見つけ方(チェックリストで順番固定)
実行計画を前にして迷うときは、「何から見るか」が定まっていないことが多いです。
ここでは見る順番を固定し、各項目の最後に次の視点へつなげます。
慣れるまでは、毎回このチェックリスト通りに“機械的に”当たりを付けるのがおすすめです。
最初に見る順(この4つで当たりを付ける)
最初は次の順で、重い候補を絞り込みます。
- TABLE ACCESS FULL(本当に必要?)
- JOIN方式(データ量に合っている?)
- Rows/Cost(推定ズレの兆候は?)
- SORT(大きい並べ替えがない?)
この順番で見ると、原因の「入口」を外しにくくなります。
① TABLE ACCESS FULLに注意
TABLE ACCESS FULLが見えたら、まず「その表をほぼ全件読む必要があるのか」を考えます。
- WHERE条件はあるのに全表走査 → 条件が効いていない可能性
- 取得列が多い/SELECT * → 不要列でI/Oが増えている可能性
- 絞り込みが結合後に起きている → JOIN順の影響の可能性
実務では「このFULLは許容か?」を決めるために、表の規模(行数・ブロック数)や、対象SQLの頻度(1回/日なのか、1秒に数回なのか)も合わせて見ます。
許容かどうかは“絶対”ではなく、頻度とSLA(許容時間)で決まります。
次に見る場所:JOIN方式(結合の親Operation)を確認し、どの順番で表が読まれているかを追います。
② JOIN方式を確認
JOINが遅いときは、方式そのものより「前提となる件数」が原因のことが多いです。
- NESTED LOOPSで外側が大きい → 内側検索が繰り返されすぎている
- HASH JOINで片方が巨大 → ハッシュ表が膨らみ、メモリ不足で遅くなる
- MERGE JOINでソートが発生 → ソートコストが主役になっている
ここでのコツは「JOIN方式の善悪」を議論する前に、外側・内側(またはビルド側)のRowsを確認し、そもそも入力が大きすぎないかを疑うことです。
もし入力が大きいなら、JOIN方式を変えても焼け石に水になりがちです。
次に見る場所:Rows(推定行数)がどこで膨らんだかを、子→親で辿ります。
③ Rows/Costで“推定ズレ”を疑う
Rowsは推定値ですが、計画を読むうえで強力なヒントになります。
次の兆候があるときは「推定が外れている」可能性を疑い、統計や条件を見直す価値が高いです。
- 子から親に上がる途中でRowsが急増している
- 絞っているはずの条件なのにRowsが大きい
- インデックスを使っているのにRowsが大量で、結果として表アクセスが多い
Costも参考になりますが、Costが小さくても遅いことはあります(FAQで扱います)。
Rowsの“急増地点”が見つかったら、次は「なぜ絞れないのか」をPredicateで確認します。
推定ズレの典型原因は次のとおりです。
- 列の値が偏っている(統計が平均化してしまう)
- 述語が複雑(OR、関数、暗黙変換)
- 結合による行数増加を見積もりにくい
次に見る場所:Predicate(条件)が“効く形”になっているかを確認します。
④ SORTが多い/大きい場合
SORTが見えたら、まず「なぜ並べ替えが必要になったのか」を確認します。
- ORDER BY が本当に必要か(画面表示だけなら別手段はないか)
- GROUP BY / DISTINCT で余計に大きい集合を作っていないか
- JOINの結果が膨らんだ後にソートしていないか
SORTは前段のRows削減が効きやすいので、先に絞る・結合順を変えるだけで改善するケースも多いです。
また、索引で“並び順”を満たせる場合は、SORT自体を回避できることもあります。
補足として、SORTは「必要なSORT」か「不要に膨らんだ結果に対するSORT」かで打ち手が変わります。
実例で学ぶ遅いSQLの典型(1ケース1メッセージ)
ここでは「1ケースで1つの学び」を狙って、実行計画からボトルネックを特定し、改善までの流れを示します。
例はあくまで典型なので、あなたのSQLに置き換えて“同じ順番で確認”できることが目的です。
問題点:どのOperationがボトルネックか
典型的に多いのは、次の流れです。
- 条件が効かずに TABLE ACCESS FULL が発生
- その結果、JOINの入力が膨らみ HASH JOIN(またはNESTED LOOPS)が重くなる
- 最後に SORT でとどめを刺される
このときのポイントは「最初に悪さをしているのはどこか」を見つけることです。
多くの場合、主犯はJOINやSORTそのものではなく、その前段で絞れずにRowsが増えている箇所です。
見つけ方のコツは次のとおりです。
- 子→親でRowsの変化を追い、急増している地点を特定する
- その地点のPredicateを見て、条件が効く形かを確認する
- FULLが出ているなら「本当に全件必要か」を再確認する
よくあるパターンとして、「A表は絞れているのに、B表側が絞れずにJOIN入力が膨らむ」ケースがあります。
この場合、B表側の条件(または結合キー)に焦点を当てるのが近道です。
改善例:具体的な打ち手と“なぜ効くか”
改善は「重いOperationを直接叩く」より、前段の入力を減らす方が効くことが多いです。
たとえば次のような方向性があります。
- WHERE条件を「インデックスが使える形」に整える(列に関数を掛けない、型変換を避ける)
- 複合インデックスを、絞り込みに使う列の並びで見直す
- JOINの順番を、まず小さい集合を作ってから結合する発想にする
- 不要なORDER BY / DISTINCTを削る、または対象行を減らしてから並べ替える
なぜ効くか:入力Rowsが減ると、JOIN・SORT・表アクセスの全てが軽くなるためです。
特にJOINは「繰り返し回数」が減ると効きが大きいです。
SORTは入力が半分になると、体感は半分以上軽くなることがあります。
補足として、改善の検討では「どの変更が最も入力Rowsを減らすか」を軸にすると、最短で効果が出やすくなります。
改善の引き出し(手段の整理棚:適用条件つき)
ここでは、よく効く改善手段を「いつ使うか(適用条件)」付きで整理します。
実行計画で当たりを付けたあと、どの引き出しを開けるべきかを素早く選べるようにするのが狙いです。
本文の読み進め中に「この状況はどれに当てはまる?」と照合できる形にしておくと、判断が早くなります。
フィルタ条件の見直し(関数・型変換・SARGable)
適用条件:WHEREで絞っているのにFULLになる/インデックスが効かないときです。
チェックポイントは次のとおりです。
- 列に関数を掛けていないか(例:TO_CHAR(col) のような形)
- 文字列と数値の比較など、暗黙の型変換が起きていないか
- LIKE ‘%xxx’ のように先頭がワイルドカードになっていないか
- NVL(col, …) などで列側を加工していないか
- OR条件がアクセスパスを不安定にしていないか
狙い:Oracleがインデックスを選びやすい条件に整えて、表全体を読まないようにします。
条件の見直しは副作用が少なく、最初の一手になりやすい改善です。
補足として、SARGable(索引が使える形)を意識すると、同じ条件でも実行計画が大きく変わることがあります。
インデックス設計(複合・選択度・カバリング)
適用条件:対象行は少ないはずなのにアクセスが重い/ROWID往復が多いときです。
考え方は次のとおりです。
- 複合インデックスは「よく絞る列」を先頭に置くのが基本(ただし利用パターン次第)
- 選択度(どれだけ絞れるか)が低い列だけでは効果が出にくい
- 取りたい列が限られるなら、インデックスだけで完結する(カバリング)と表アクセスを減らせる
- ORDER BY の並びに合わせると、SORT回避に効くこともある
注意:インデックスは増やしすぎると更新コストが上がるため、効果と副作用をセットで評価します。
更新が多い表は特に、追加の判断を慎重にします。
補足として、複合インデックスは「列の順番」で効きが大きく変わります。
現行のWHERE条件と結合条件の使われ方(どの列が頻出か)を基準に設計します。
JOIN/集計の作り替え(前処理・サブクエリ・CTE)
適用条件:JOIN後にRowsが膨らんでから絞っている/集計やSORTが巨大なときです。
方針例は次のとおりです。
- 先に絞れる表を先に絞る(前処理して小さくしてから結合)
- 目的が「存在確認」なら、必要以上に列を引っ張らない
- 集計粒度(GROUP BYのキー)を見直し、無駄に細かくしない
- DISTINCTで重複排除しているなら、そもそも重複が出る原因を疑う
狙い:処理順序を作り替えて、「重い処理に入る前の入力」を小さくします。
補足として、集計や重複排除は“後ろで何とかする”処理になりがちです。
前段でRowsを制御できる構造にすると、改善が長持ちしやすくなります。
チューニングの基本手順(再現できる手順書)
SQLチューニングは勘に見えがちですが、手順化すると再現性が上がります。
ここでは、現場で迷いにくいテンプレとしてまとめます。
個別のテクニックよりも「進め方」が安定すると、改善の成功率が上がります。
手順1:現象整理(再現条件・対象SQL・計測)
最初に、改善対象をブレなく定義します。
- 遅いのは「いつ」「どのSQL」「どの条件(バインド値)」か
- 実行時間の測り方(同条件で複数回、キャッシュ影響の考慮)
- 影響範囲(オンラインかバッチか、ピーク時間帯か)
ここが曖昧だと、改善したつもりが“別の条件では悪化”という事故が起きます。
まずは“同じ条件で再現できる”状態を作るのが最優先です。
補足として、可能なら次を用意すると改善検証が安定します。
- 代表的なバインド値(遅いケース/速いケース)
- 実行回数と中央値(外れ値の影響を減らす)
手順2:実行計画で仮説→優先度付け
次に、実行計画から仮説を立てて、優先度を決めます。
- FULL / JOIN / SORT の「重い候補」を列挙する
- Rowsの急増点を見つけて、主犯候補を絞る
- Predicateを見て、条件が効かない理由を推測する
優先度は「入力Rowsを減らせそうな場所」から付けると、効果が出やすいです。
迷ったら、まずはFULLの原因や絞り込み条件の形から手を付けると、改善が早いことが多いです。
補足として、改善案が複数あるときは次の観点でスコアリングすると判断がぶれにくくなります。
- 効果の大きさ(Rows削減幅)
- 実装コスト(変更量、レビュー難度)
- リスク(副作用、更新負荷)
手順3:改善→検証→差分管理
改善は“小さく”行い、必ず検証と差分管理をセットにします。
- 変更は1回に1つ(条件修正、索引追加など)
- 同条件で再計測し、改善幅を数値で確認する
- 実行計画がどう変わったか(変わっていないのに速い/遅いも含めて)を記録する
この手順を守ると、原因の切り分けが速くなり、戻しも簡単になります。
改善前後で「計画は変わったが遅い」「計画は同じだが速い」も起こり得るので、必ず“計画+実測”のセットで判断します。
補足として、差分管理の観点では、実行計画(テキスト)と実測、変更内容を1つのチケットやメモにまとめておくと、後からの説明が楽になります。
FAQとまとめ(結論→理由→対処で短く)
最後に、実行計画を扱うときに出やすい疑問を、テンプレで短く整理します。
困ったときに戻ってこられる“安全装置”として使ってください。
FAQ1:Costが低いのに遅いのはなぜ?
結論:Costは“推定”であり、実行時の待機や環境要因を反映しないためです。
理由は次のとおりです。
- Costは主に統計情報を元にした見積もりで、I/O待ちやロック待ちを直接は表しません
- 推定Rowsが外れていると、Costも当てになりにくくなります
対処:Rowsが不自然でないか(推定ズレ)を確認し、実行時統計や待機イベントなど実測側で“何待ちか”を確認します。
- 変更前後で計画が同じかどうかも合わせて確認する
FAQ2:実行計画が変わるのはなぜ?
結論:統計・値・環境が変わると、最適な経路が変わるためです。
理由は次のとおりです。
- 統計情報の更新やデータ偏りで、選択度の見積もりが変わる
- バインド値により「絞れる/絞れない」が変わる
- インデックス追加/削除、パラメータ変更などで候補が変わる
対処:いつから変わったか(変更点)を整理し、推定Rowsの変化点とPredicateを確認します。
必要に応じて統計情報の見直しやSQLの書き方を調整します。
FAQ3:統計情報はいつ更新すべき?
結論:データ分布が変わって推定Rowsが外れるときが更新のサインです。
理由として、統計が古いとOracleの見積もりが現実とズレやすくなります。
対処:変更量が大きい表や、性能劣化が出たタイミングで更新を検討し、更新後は実行計画と性能がどう変わったかを必ず確認します。
まとめ:今日からのチェック3点
まずはアクセス経路(FULLか)を見ます。
次にJOIN方式と、その前提のRowsを追います。
最後にSORTの有無と大きさを確認し、入力Rowsを減らす方向で改善を考えます。
最後に一言:実行計画は“慣れ”が効きますが、慣れを支えるのは手順です。
毎回同じ順番で読んで、同じ観点で仮説を立てて、同じやり方で検証する——これだけで、SQLチューニングの成功率は大きく上がります。