私たちは何か間違ったことをしていますか、それともSQL Serverエラーですか?
これは間違った結果のバグであり、通常のサポートチャネルから報告する必要があります。サポート契約を結んでいない場合は、Microsoftがバグとして動作を確認した場合に、有料のインシデントが通常払い戻されることを知ることが役立つ場合があります。
このバグには3つの要素が必要です。
- 外部参照を持つネストされたループ(適用)
- 外部参照をシークする内側の遅延インデックススプール
- 内側の連結演算子
たとえば、質問のクエリは次のような計画を作成します。
これらの要素の1つを削除する方法は多数あるため、バグは再現されなくなりました。
たとえば、オプティマイザがレイジーインデックススプールを使用しないことを選択することを意味するインデックスまたは統計を作成できます。または、ヒントを使用して、連結を使用する代わりに、ハッシュまたはマージ結合を強制することができます。クエリを書き換えて同じセマンティクスを表現することもできますが、その場合、1つ以上の必要な要素が欠落している異なるプラン形状になります。
詳細
遅延インデックススプールは、外部参照(相関パラメーター)値でインデックス付けされた作業テーブルに、内部結果行を遅延キャッシュします。レイジーインデックススプールが以前に見た外部参照を要求された場合、ワークテーブルからキャッシュされた結果行をフェッチします(「巻き戻し」)。スプールがこれまでに見たことのない外部参照値を要求された場合、現在の外部参照値でサブツリーを実行し、結果をキャッシュします(「再バインド」)。レイジーインデックススプールのシーク述語は、その作業テーブルのキーを示します。
この外部の参照が以前に見たものと同じであるかどうかをスプールがチェックするときに、この特定の計画形状で問題が発生します。Nested Loops Joinは、外部参照を正しく更新し、PrepRecompute
インターフェイスメソッドを介して内部入力のオペレーターに通知します。このチェックの開始時に、内部演算子はCParamBounds:FNeedToReload
プロパティを読み取り、外部参照が前回から変更されたかどうかを確認します。スタックトレースの例を以下に示します。
上記のサブツリーが存在する場合、具体的には連結が使用される場合CParamBounds:FNeedToReload
、外部参照が実際に変更されたかどうかに関係なく、常にfalseを返すようなバインディングで何らかの問題(おそらくByVal / ByRef / Copyの問題)が発生します。
同じサブツリーが存在するが、マージユニオンまたはハッシュユニオンが使用される場合、この必須プロパティは各反復で正しく設定され、レイジーインデックススプールは必要に応じて毎回巻き戻しまたは再バインドします。ちなみに、Distinct SortとStream Aggregateには問題はありません。私の疑いは、MergeとHash Unionが以前の値のコピーを作成するのに対して、連結は参照を使用することです。残念ながら、SQL Serverのソースコードにアクセスせずにこれを検証することはほぼ不可能です。
最終的な結果として、問題のあるプラン形状のレイジーインデックススプールは、常に現在の外部参照を既に見ていると考え、作業テーブルをシークすることで巻き戻し、通常は何も検出しないため、その外部参照に対して行は返されません。デバッガーで実行をステップ実行すると、スプールはそのRewindHelper
メソッドのみを実行し、そのメソッドは実行しませんReloadHelper
(このコンテキストではreload = rebind)。これは、実行計画で明らかです。スプールの下の演算子はすべて「実行数= 1」であるためです。
もちろん、例外は、Lazy Index Spoolが指定される最初の外部参照です。これにより、常にサブツリーが実行され、結果テーブルが作業テーブルにキャッシュされます。後続のすべての反復では巻き戻しが行われ、現在の反復の外部参照の値が最初と同じ場合にのみ行(単一のキャッシュされた行)が生成されます。
したがって、ネストされたループ結合の外側に設定された任意の入力に対して、クエリは、処理された最初の行の複製と同じ数の行を返します(もちろん最初の行自体に1行を加えます)。
デモ
テーブルとサンプルデータ:
CREATE TABLE #T1
(
pk integer IDENTITY NOT NULL,
c1 integer NOT NULL,
CONSTRAINT PK_T1
PRIMARY KEY CLUSTERED (pk)
);
GO
INSERT #T1 (c1)
VALUES
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6);
次の(簡単な)クエリは、Merge Unionを使用して、各行(合計18)の正しいカウント2を生成します。
SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY
(
SELECT COUNT_BIG(*) AS c1
FROM
(
SELECT T1.c1
UNION
SELECT NULL
) AS U
) AS C;
クエリヒントを追加して連結を強制する場合:
SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY
(
SELECT COUNT_BIG(*) AS c1
FROM
(
SELECT T1.c1
UNION
SELECT NULL
) AS U
) AS C
OPTION (CONCAT UNION);
実行計画には問題のある形状があります。
そして、結果は今では不正確で、たった3行です。
この動作は保証されていませんが、クラスター化インデックススキャンの最初の行のc1
値は1です。この値を持つ行は他に2つあるため、合計で3つの行が生成されます。
次に、データテーブルを切り捨てて、「最初の」行の重複を追加してロードします。
TRUNCATE TABLE #T1;
INSERT #T1 (c1)
VALUES
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6),
(1), (1), (1), (1), (1), (1);
連結計画は次のとおりです。
そして、示されているように、8行が生成されますc1 = 1
。
このバグのConnectアイテムを開いていることに気付きましたが、実際には、本番環境に影響を与える問題を報告する場所ではありません。その場合は、Microsoftサポートに連絡する必要があります。
この誤った結果のバグは、ある段階で修正されました。2012年以降、SQL Serverのどのバージョンでも再現されなくなりました。SQL Server 2008 R2 SP3-GDRビルド10.50.6560.0(X64)で再現します。