これまでのエントリに記載してきたように、実行計画は階層的なツリー構造で表現
されます。
本エントリでは、実行計画の読み方のfirst stepとして、
実際の処理順序に即したツリー構造のたどり方を説明します。
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, ‘LAST’));
PLAN_TABLE_OUTPUT
———————————————————————————————
SQL_ID dvv5bah5b4k51, child number 1
————————————-
SELECT /*+ FULL(PA) INDEX(CH idx_chpa) USE_NL(CH) LEADING(PA) */ cid,
cname, pa.pid, pname FROM ch, pa WHERE ch.pid = pa.pid and pa.pid
= 1
Plan hash value: 3514264536
—————————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
—————————————————————————————–
| 0 | SELECT STATEMENT | | | | 20 (100)| |
| 1 | NESTED LOOPS | | 10 | 40120 | 20 (0)| 00:00:01 |
|* 2 | TABLE ACCESS FULL | PA | 1 | 2004 | 9 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| CH | 10 | 20080 | 11 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IDX_CHPA | 10 | | 1 (0)| 00:00:01 |
—————————————————————————————–
Predicate Information (identified by operation id):
—————————————————
2 – filter(“PA”.”PID”=1)
4 – access(“CH”.”PID”=1)
24 rows selected.
### 実行計画のツリー構造とオペレーションの親子関係
実行計画は、一般に複数のオペレーションから構成されます。オペレーションは、親子関係に
したがってインデントして整形されたツリー構造となります。
たとえば、上記の実行計画は、NESTED LOOPS, TABLE ACCESS FULL などのオペレーション
から構成されます。(オペレーションの種類と処理内容については次のエントリで説明します。)
オペレーションの親子関係についてですが、
Id=1 NESTED LOOPSは Id=2 TABLE ACCESS FULLと、Id=3 TABLE ACCESS BY INDEX ROWID
の親となります。同様に Id=3 TABLE ACCESS BY INDEX ROWID
Id= 4 INDEX RANGE SCAN の親となります。
Id=0 SELECT STATEMENT
↑
│行ソース
│
Id=1 NESTED LOOPS
↑ ↑
行ソース┌─────┘ └─────┐行ソース
│ │
Id=2 TABLE ACCESS FULL Id=3 TABLE ACCESS BY INDEX ROWID
↑
│行ソース
│
Id=4 INDEX RANGE SCAN
それぞれのオペレーションは、行ソースとよばれる表から取得した行データ(のサブセット)を出力します。
親オペレーションは子オペレーションの行ソースを入力として処理を行います。
概念的な説明だけではわかりにくいので、先の実行計画がどのように処理されるかを
イメージしながら、読み方を説明してみましょう。
### 実行計画で一番最初に実行されるオペレーションを探す
まず、この実行計画で一番最初に実行される(*1) オペレーションを探します。
最も最上位のオペレーション(Id=0 SELECT STATEMENT)にポイントしてから、そのオペレーションの
子オペレーションをたどります。
子オペレーションが2つ以上ある場合は、上側に表記されたオペレーションをたどります。
子の子、子の子の子、とインデントされたオペレーションを順にたどってゆき、
子がなくなった時点でポイントしていたオペレーションが、
実行計画で一番最初に実行されるオペレーションです。
先の実行計画の例において、一番最初にオペレーションを特定するまでの手順は以下の
とおりです。
* 1\. 最も最上位のオペレーション(Id=0 SELECT)をポイント
* 2\. Id=0 SELECTの子オペレーションはId=1 NESTED LOOPSだけなので、NESTED LOOPSをポイント
* 3\. Id=1 NESTED LOOPSの子オペレーションとして、2つのオペレーション
Id=2 TABLE ACCESS FULL と
Id=3 TABLE ACCESS BY INDEX ROWID があるが、上側に表示されている
Id=2 TABLE ACCESS FULLをたどる。
* 4\. Id=2 TABLE ACCESS FULL には、子オペレーションがないため、
このオペレーションがこの実行計画で最初に実行されるオペレーションとなる
一番深くインデントされたオペレーションが必ずしも一番最初に実行される
わけではないことに注意が必要です。
上記の例でも、一番深くインデントされたオペレーションである
Id=4 INDEX RANGE SCAN は一番最初に実行されるオペレーションではありません。
* (*1)「実質的な意味で」一番最初に実行されるオペレーションという意味です。
形式的には、Id=0のSELECT STATEMENTが一番最初に実行されるオペレーションですが、この観点は理解するうえであまり意味がないからです。
### 以後のオペレーション実行順序
さて、最初に実行されるオペレーションが Id=2 TABLE ACCESS FULL
であることはわかりました、以降、どのような順序でオペレーションが実行される
のでしょうか。順に追っていきましょう。
* 5\. Id=2 TABLE ACCESS FULL は、PA表をテーブルスキャン(全行取得)する
オペレーションであり、取得した結果を親オペレーションId=1 NESTED LOOPに
渡す。
* 6\. Id= 1 NESTED LOOP は、自分の子オペレーションのうち、次に実行すべき
オペレーションを探す。ここでは、Id= 3 TABLE ACCESS BY INDEX ROWIDが
未実行であるため、Id= 3 TABLE ACCESS BY INDEX ROWID をポイントする。
* 7\. Id= 3 TABLE ACCESS BY INDEX ROWID は自分の子オペレーションである、
Id= 4 INDEX RANGE SCAN をポイントする。
* 8\. Id= 4 INDEX RANGE SCAN には子オペレーションがないため、
Id= 4 INDEX RANGE SCAN の処理を実行する。
索引IDX_CHPAをレンジスキャン(範囲検索)し、表に格納された行の識別子
である、ROWIDのリストを得て、取得した結果を親オペレーション
Id= 3 TABLE ACCESS BY INDEX ROWID に渡す。
* 9\. Id= 3 TABLE ACCESS BY INDEX ROWI Dは
Id= 4 INDEX RANGE SCAN より得たROWIDの
リストに従い、表から行データを取得し、取得した結果を親オペレーション
Id= 1 NESTED LOOPに渡す。
* 10\. Id= 1 NESTED LOOP のすべての子オペレーションの実行が完了したため、
自オペレーションの処理である Nested Loop 結合処理を実行して、結果を
親オペレーションId=0 SELECTに渡す。
* 11\. Id=0 SELECTは擬似的なオペレーションであり、実際の処理は行わないため、ここで終了
このような順序となります。
### ルール化するならば
先に説明した手順をルール化するならば、以下のようにまとめられます。
(わかりやすくするため厳密性を犠牲にしています。)
* 1\. 自オペレーションの処理は、すべての子オペレーションの処理が完了してから実行される。
* 2\. 子オペレーションがない場合、自オペレーションを実行できる。
* 3\. あるオペレーションに子オペレーションが複数ある場合、上側に記載された子
オペレーションが先に実行される。
[…] « SQLチューニングアドバイザの代用品としての動的サンプリング 実行計画のツリーのたどり方 (実行計画の読み方#1) […]