追加: SQL Server 2012では、この領域でパフォーマンスがいくらか改善されていますが、下記の特定の問題に対処しているようには見えません。これは、
SQL Server 2012 以降の次のメジャーバージョンで修正される予定です。
計画は、単一の挿入がパラメーター化された手順(おそらく自動パラメーター化)を使用していることを示しているため、これらの解析/コンパイル時間は最小限に抑えられます。
もう少し詳しく調べたいと思ったので、ループ(スクリプト)を設定して、VALUES
句の数を調整し、コンパイル時間を記録してみました。
次に、コンパイル時間を行数で割って、句ごとの平均コンパイル時間を取得しました。結果は以下のとおりです
250 VALUES
句までは、コンパイル時間/句の数がわずかに増加する傾向がありますが、あまり劇的ではありません。
しかし、その後突然の変化があります。
データのそのセクションを以下に示します。
+------+----------------+-------------+---------------+---------------+
| Rows | CachedPlanSize | CompileTime | CompileMemory | Duration/Rows |
+------+----------------+-------------+---------------+---------------+
| 245 | 528 | 41 | 2400 | 0.167346939 |
| 246 | 528 | 40 | 2416 | 0.162601626 |
| 247 | 528 | 38 | 2416 | 0.153846154 |
| 248 | 528 | 39 | 2432 | 0.157258065 |
| 249 | 528 | 39 | 2432 | 0.156626506 |
| 250 | 528 | 40 | 2448 | 0.16 |
| 251 | 400 | 273 | 3488 | 1.087649402 |
| 252 | 400 | 274 | 3496 | 1.087301587 |
| 253 | 400 | 282 | 3520 | 1.114624506 |
| 254 | 408 | 279 | 3544 | 1.098425197 |
| 255 | 408 | 290 | 3552 | 1.137254902 |
+------+----------------+-------------+---------------+---------------+
直線的に増加していたキャッシュされたプランのサイズは突然減少しますが、CompileTimeは7倍に増加し、CompileMemoryは飛躍的に増加します。これは、自動パラメーター化された計画(パラメーターが1,000個)と非パラメーター化計画の間のカットオフポイントです。その後は、(一定の時間内に処理される値句の数に関して)線形的に効率が低下するようです。
なぜそうなのかわからない。おそらく、特定のリテラル値の計画をコンパイルしているときは、線形にスケーリングしないアクティビティ(ソートなど)を実行する必要があります。
完全に重複する行で構成されるクエリを試したとき、キャッシュされたクエリプランのサイズに影響を与えていないようで、定数のテーブルの出力の順序にも影響しません(ソートに費やされたヒープ時間に挿入しているため)。とにかく意味がありません)。
さらに、クラスター化インデックスがテーブルに追加された場合でも、プランには明示的な並べ替え手順が表示されるため、実行時の並べ替えを回避するために、コンパイル時に並べ替えられているようには見えません。
デバッガーでこれを確認しようとしましたが、私のバージョンのSQL Server 2008のパブリックシンボルが利用できないようです。代わりにUNION ALL
、SQL Server 2005の同等の構成を確認する必要がありました。
典型的なスタックトレースは以下のとおりです
sqlservr.exe!FastDBCSToUnicode() + 0xac bytes
sqlservr.exe!nls_sqlhilo() + 0x35 bytes
sqlservr.exe!CXVariant::CmpCompareStr() + 0x2b bytes
sqlservr.exe!CXVariantPerformCompare<167,167>::Compare() + 0x18 bytes
sqlservr.exe!CXVariant::CmpCompare() + 0x11f67d bytes
sqlservr.exe!CConstraintItvl::PcnstrItvlUnion() + 0xe2 bytes
sqlservr.exe!CConstraintProp::PcnstrUnion() + 0x35e bytes
sqlservr.exe!CLogOp_BaseSetOp::PcnstrDerive() + 0x11a bytes
sqlservr.exe!CLogOpArg::PcnstrDeriveHandler() + 0x18f bytes
sqlservr.exe!CLogOpArg::DeriveGroupProperties() + 0xa9 bytes
sqlservr.exe!COpArg::DeriveNormalizedGroupProperties() + 0x40 bytes
sqlservr.exe!COptExpr::DeriveGroupProperties() + 0x18a bytes
sqlservr.exe!COptExpr::DeriveGroupProperties() + 0x146 bytes
sqlservr.exe!COptExpr::DeriveGroupProperties() + 0x146 bytes
sqlservr.exe!COptExpr::DeriveGroupProperties() + 0x146 bytes
sqlservr.exe!CQuery::PqoBuild() + 0x3cb bytes
sqlservr.exe!CStmtQuery::InitQuery() + 0x167 bytes
sqlservr.exe!CStmtDML::InitNormal() + 0xf0 bytes
sqlservr.exe!CStmtDML::Init() + 0x1b bytes
sqlservr.exe!CCompPlan::FCompileStep() + 0x176 bytes
sqlservr.exe!CSQLSource::FCompile() + 0x741 bytes
sqlservr.exe!CSQLSource::FCompWrapper() + 0x922be bytes
sqlservr.exe!CSQLSource::Transform() + 0x120431 bytes
sqlservr.exe!CSQLSource::Compile() + 0x2ff bytes
したがって、スタックトレースの名前から離れると、文字列の比較に多くの時間を費やしているように見えます。
このKB記事は、クエリ処理の正規化段階DeriveNormalizedGroupProperties
と呼ばれていたものに関連していることを示しています
このステージは現在バインドまたは代数化と呼ばれ、前の解析ステージからの式解析ツリー出力を受け取り、代数化された式ツリー(クエリプロセッサツリー)を出力して最適化(この場合は簡単な計画最適化)に進みます[参照]。
元のテストを再実行するためにもう1つの実験(スクリプト)を試しましたが、3つの異なるケースを調べました。
- 重複がない、長さが10文字の姓と名の文字列。
- 重複がない、長さが50文字の姓と名の文字列。
- 長さが10文字の姓と名の文字列で、すべて重複しています。
文字列が長くなればなるほど、事態は悪化し、逆に、複製が多ければ多いほど、事態は改善することがはっきりとわかります。前述のように、重複はキャッシュされた計画サイズに影響を与えないので、代数表現ツリー自体を構築するときに重複識別のプロセスが必要であると思います。
編集する
この情報が活用される 1つの場所は、@ Lievenによってここに示されています
SELECT *
FROM (VALUES ('Lieven1', 1),
('Lieven2', 2),
('Lieven3', 3))Test (name, ID)
ORDER BY name, 1/ (ID - ID)
コンパイル時にName
列に重複がないと判断できるため、1/ (ID - ID)
実行時に2次式による順序付けをスキップし(プランのソートにはORDER BY
列が1つしかない)、ゼロ除算エラーは発生しません。重複がテーブルに追加された場合、ソート演算子は列による2つの順序を示し、予期されるエラーが発生します。