実行計画の解析方法(1)
本ページでは単一テーブルに対しての実行計画の解析方法を記載します。実行計画を読む上での前提知識
・実行計画はインデントの深いものから順番に実行される。・テーブル間の結合(ネステッドループ結合、ソートマージ結合、ハッシュ結合、直積結合)は一つずつ結合される。
・実行計画を立てる際にORACLEはパフォーマンスがよい実行計画が立てられるように内部的にSQL文を書き換える場合がある。(ビューのマージや抽出条件の追加等) なお、この内部的な書き換えはヒントや初期化パラメータにより制御することができる。
・コストベースで実行計画が立てられた場合該当オペレーションに対する行数(rows)、処理時間等(Time)が表示されるがこの値は実測値ではなく予測のため正しいとは限らない。(大抵の場合正確ではない)
・実行計画上のコストが小さいにもかかわらず実際の処理時間が長い場合は実行計画が最善であるか見直したほうがよい。
本ページで使用した検証用データの定義
本ページで記載されているSQLは全て以下のSQLで作成されたテーブルとインデックスを使用しています。CREATE TABLE AAA (COL1 NUMBER ,COL2 NUMBER); ALTER TABLE AAA ADD CONSTRAINT PK_AAA PRIMARY KEY(COL1); BEGIN FOR I IN 1..10000 LOOP INSERT INTO AAA VALUES(I,I); COMMIT; END LOOP; END; / EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'TEST',TABNAME=>'AAA',CASCADE=>TRUE);
例1 テーブルフルスキャン
この実行計画はAAAというテーブルに対してテーブルフルスキャンを実行する実行計画です。 Id=0の「SELECT STATEMENT」はSELECT文を実行していることを表しているだけで実行計画的な意味はありません。 Id=1は「TABLE ACCESS FULL」の記載と、Name=AAAの記載からAAAというテーブルをフルスキャンしていることが読み取れます。 なお、RowsやTimeはCBOの見積もりであって実測値ではありません。見積もり値は統計情報が正確でもSQLが複雑になるほど精度が落ちるので注意してください。また、Id=1のOperationは一段落下がっていますが、実行計画は段落が深いオペレーションから実施されるため以下の例は1→0の順番に実行されることになります。
select * from aaa; 実行計画 ---------------------------------------------------------- Plan hash value: 864433273 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 10000 | 80000 | 8 (0)| 00:00:01 | | 1 | TABLE ACCESS FULL| AAA | 10000 | 80000 | 8 (0)| 00:00:01 | --------------------------------------------------------------------------
例2 インデックスユニークスキャン
この実行計画のシナリオは以下のように処理します。①PK_AAAを使用したインデックスユニークスキャンによりCOL1=1の行のrowidを取得する(id=2の箇所)。
PK_AAAはプライマリキーのインデックスのためインデックススキャンではなくインデックスユニークスキャンになります。 また、「COL1=1」という条件は下にあるPredicate Informationより読み取れます。Predicate InformationがあるオペレーションはIdの左に「*」がついています。
②①で取得したrowidに対応するブロックを読み込み、その中にあるCOL1=1の行データを読み込む。(id=1の箇所)
rowidはその行に対するポインタのようなものでrowidが分かればダイレクトに該当データブロックの該当行にアクセスすることができます。 ただし、I/Oの最小単位はブロックのため必ず1行のデータが数バイトであったとしても必ずブロック単位のI/Oが発生します。
select * from aaa where col1 = 1;
実行計画
----------------------------------------------------------
Plan hash value: 3774071719
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 8 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| AAA | 1 | 8 | 2 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | PK_AAA | 1 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("COL1"=1)
例3 インデックスファストフルスキャン
この実行計画は以下のように処理します。①PK_AAAをインデックスファストフルスキャンによりぜんブロック読み込む(Id=2)。
インデックスファストフルスキャンとは通常のインデックスアクセスのように抽出対象行のrowidを検索するためのアクセス方法ではなく、 索引ブロックのみ読み込めば対象SQLが処理できるようなケースで使用される実行計画です。このSQLはaaa表の件数だけ分かれば結果を返せるSQLであり、 PK_AAAはプライマリキー索引であることからNULLデータがなく索引ブロックの読み込むだけでテーブルの行数が確認できるためインデックスファストフルスキャンを使用して読み込みブロックを削減することができます。
②①で抽出した件数をカウントする(Id=1)
「SORT AGGREGATE」はSUM()やCOUNT()等の集計関数の集計を行う際に使用されるオペレーションです。このSQLの場合はCOUNT(*)の結果を集計しています。 ①で抽出される予想行数(Rows)は1万件でしたが、②で集計にかけることで予想行数は1行まで絞られています。
select count(*) from aaa; 実行計画 ---------------------------------------------------------- Plan hash value: 1020877103 ------------------------------------------------------------------------ | Id | Operation | Name | Rows | Cost (%CPU)| Time | ------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | 7 (0)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | | | | 2 | INDEX FAST FULL SCAN| PK_AAA | 10000 | 7 (0)| 00:00:01 | ------------------------------------------------------------------------

折りたたみ
展開