実行プランを読む - 基本編 (その 1)

 

神谷 雅紀
Escalation Engineer

 

前回 CXPACKET 待ちは悪いことか?で、クエリの実行について触れました。クエリがどのように実行されているのかを知るためには、実行プランを見る必要がありますが、実行プランの見方について解説している文書があまりないため、今回は、実行プランの基本的な見方について説明したいと思います。

 

実行プランとは

実行プランとは、SQL Server がクエリの結果を得るために、そのクエリをどのように処理するのかを表したものです。例えば、select c1, c2 from tabA where c3=1 というクエリの結果を得るために、tabA テーブルの非クラスタ化インデックス ind_tabA を検索して行を絞り込み、その絞り込んだ行をクラスタ化インデックスから読み取り、読み取った行の中から c1, c2 の 2 列の値を取り出す、といったような、クエリ結果を得るためのプロセスを表しています。

実行プランは、内部的には、tree 構造を持つメモリ上のオブジェクトです。SQL Server で表現される場合は、tree 構造の表現として一般的に使われる、上から下へ伸びていく形ではなく、下の図のように、左上にルートを置き、右側に伸びていく形で表現されます。

 

tree

 

制御フローとデータフロー

この制御フローとデータフローという表現は、Microsoft 社内では聞いたことはありません。この言葉は、数年前にシアトルで開催されていた PASS Conference (PASS = SQL Server ユーザー会) のあるセッションで使われていた言葉ですが、分かりやすい表現だと思いますので、ここでもこの言葉を使いたいと思います。

実行プラン内の各オペレータ (上の例では、Nested Loops や Index Seek) は、3 つの主要なメソッドを持っています。それは、Open, Fetch (GetRow), Close です。各メソッドは以下の働きをします。(Fetch という言い方はカーソル風、GetRow という言い方は OLE DB 風でしょうか。どちらでも構いません。)

メソッド 働き
Open そのオペレータが最初の行を返せる状態にする。
Fetch/GetRow 1 行を呼び出し元に返す。
Close 使用していたリソースの解放などの後始末を行う。

上位のオペレータが下位のオペレータのこれらメソッドを呼び出すのが、制御フローです。一方、データは、Fetch (GetRow) 呼び出しにより、下位オペレータから上位オペレータに渡されます。つまり、制御は上から下へ流れ、データは下から上へ流れます。

クエリの実行は、必ず最上位のオペレータから始まります。

上の実行プランの例では、制御フローとデータフローは以下のようになります。

フェーズ

処理

Open

SELECT が Nested Loops の Open を呼び出す。
Nested Loops は、自分の Open 処理の一部として Index Seek の Open を呼び出す。
Index Seek は、自分の Open 処理が終わると、制御を Nested Loops に戻す。
Nested Loops は、もうひとつの下位オペレータである Key Lookup の Open を呼び出す。
Key Lookup は、自分の Open 処理が終わると、制御を Nested Loops に戻す。
Nested Loops は、自分の Open 処理が終わると、制御を SELECT へ戻す。

Fetch

SELECT は最初の行を取得するために Nested Loops の Fetch を呼び出す。
Nested Loops は、Index Seek の Fetch を呼び出す。
Index Seek は、Nested Loops に 1 行 (インデックス行) を渡す。
Nested Loops は、Key Lookup の Fetch を呼び出す。その時、Index Seek から受け取ったインデックスキーを Key Lookup に渡す。
Key Lookup は、受け取ったキーを使って行を検索し、1 行を Nested Loops に渡す。
Nested Loops は、Key Lookup から受け取った行を SELECT に渡す。
SELECT は、渡された行をクライアントへの送信バッファに書き込む。
送信バッファがいっぱいになっていれば、送信バッファの内容をクライアントへ送信する。
SELECT は、次の行を取得するために Nested Loops の Fetch を呼び出す。
Nested Loops は、Index Seek の Fetch を呼び出す。

~ 繰り返し ~

SELECT は、次の行を取得するために Nested Loops の Fetch を呼び出す。
Nested Loops は、Index Seek の Fetch を呼び出す。
Index Seek は、もう行がないこと (End Of Rowset) を Nested Loops に通知する。
Nested Loops は、もう行がないことを SELECT に通知する。

Close

SELECT は、Nested Loops の Close を呼び出す。
Nested Loops は、自分の Close 処理の一部として Index Seek の Close を呼び出す。
Index Seek は、自分の Close 処理が終わると、制御を Nested Loops に戻す。
Nested Loops は、もうひとつの下位オペレータである Key Lookup の Close を呼び出す。
Key Lookup は、自分の Close 処理が終わると、制御を Nested Loops に戻す。
Nested Loops は、自分の Close 処理が終わると、制御を SELECT へ戻す。

-

送信バッファの内容をクライアントへ送信する。

 

実行プランを見る場合に、データフローのみが着目されていることがありますが、データフローだけでなく、制御フローも意識して見ないと、クエリがどのように実行されているのかを正しく把握することはできません。

また、注意が必要な点として、クエリプロセッサにおけるデータの基本的な単位は「行」であるという点があります。「ページ」や「エクステント」などの単位はストレージエンジンでの単位であり、クエリプロセッサでは「行」がデータの単位です。オペレータ間で受け渡されるデータはすべて「行」です。