主なパフォーマンスの違い
ここでの主な違いは、より良い実行するクエリが押し下げされた上で述語を求めることをしているCodeMasterID
ビューで選択したすべての4つのテーブル(2つの一時テーブル(実際&ヒストリー))の終わりまでということをしないように思われる(フィルタ演算子)。
TL DR;
この問題は、ビューなどの特定のケースでパラメータがウィンドウ関数にプッシュダウンされないことが原因です。最も簡単な解決策は、OPTION(RECOMPILE)
可能であればオプティマイザが実行時にパラメータを「参照」できるようにビュー呼び出しに追加することです。各クエリ呼び出しの実行プランを再コンパイルするのにコストがかかりすぎる場合は、パラメーターを期待するインラインテーブル値関数を使用することが解決策になる場合があります。優れた存在であるブログ投稿によるポール・ホワイトこれには。特定の問題を見つけて解決するためのより詳細な方法については、読み続けてください。
パフォーマンスの高いクエリ
Codemasterテーブル


取引表


朝の述語の匂いが好き
大きな悪いクエリ
Codemasterテーブル


これは述語のみのゾーンです
Dealテーブル

しかし、オプティマイザは「The art of the Deal™」を読みませんでした

...そして過去から学ばない
すべてのデータがフィルター演算子に到達するまで

だから、何を与えるのですか?
ここでの主な問題は、ビュー内のウィンドウ関数が原因でオプティマイザが実行時にパラメータを「認識」せず、SelOnSeqPrj
(シーケンスプロジェクトで選択、この記事の後半で参照用に)使用できないことです。
テストサンプルを使用SP_EXECUTESQL
して同じ結果を複製し、ビューの呼び出しをパラメーター化するために使用することができました。DDL / DMLの補遺を参照
ウィンドウ関数を使用してテストビューに対してクエリを実行し、 INNER JOIN
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
その結果、CPU時間は約4.5秒、経過時間は3.2秒になります。
SQL Server Execution Times:
CPU time = 4595 ms, elapsed time = 3209 ms.
の甘い抱擁を加えるとき OPTION(RECOMPILE)
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1 OPTION(RECOMPILE)',N'@P1 INT',@P1 = 37155;
すべて良いです。
SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 98 ms.
なぜ
これはすべて@P1
、ウィンドウ関数とパラメーター化によりフィルター演算子が発生するため、述語をテーブルに適用できないという点をサポートします。

テンポラル表の問題だけではありません
補遺2を参照
テンポラルテーブルを使用していない場合でも、次のことが起こります。

次のようにクエリを記述した場合も同じ結果になります。
DECLARE @P1 int = 37155
SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1;
この場合も、オプティマイザはウィンドウ関数を適用する前に述語を押し下げていません。
ROW_NUMBER()を省略した場合
CREATE VIEW dbo.Bad3
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
すべては順調です
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad3
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 33 ms.
それで、私たちを離れるすべてはどこにありますか?
ROW_NUMBER()
フィルタが悪いクエリに適用される前に計算されます。
そして、これらすべてが
、ウィンドウ関数とビューに関するPaul Whiteによる2013年のこのブログ投稿につながります。
この例の重要な部分の1つは次のステートメントです。
残念ながら、SelOnSeqPrj簡略化ルールは、述語が定数との比較を実行する場合にのみ機能します。そのため、次のクエリは、SQL Server 2008以降で次善の計画を作成します。
DECLARE @ProductID INT = 878;
SELECT
mrt.ProductID,
mrt.TransactionID,
mrt.ReferenceOrderID,
mrt.TransactionDate,
mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt
WHERE
mrt.ProductID = @ProductID;

この部分はSP_EXECUTESQL
、ビューでパラメーターを自分で宣言したり使用したりしたときに見たものに対応しています。
実際の解決策
1:オプション(再コンパイル)
OPTION(RECOMPILE)
実行時に値を「見る」ことが可能であることがわかっています。各クエリ呼び出しの実行プランを再コンパイルするのが高すぎる場合、他の解決策があります。
2:パラメーター付きのインラインテーブル値関数
CREATE FUNCTION dbo.BlaBla
(
@P1 INT
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,
cm.ParentDeptID,d.DealID,
d.CodeMasterID as dealcodemaster,
d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = @P1
)
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.BlaBLa(@P1)',N'@P1 INT',@P1 = 37155
期待されるシーク述語をもたらす
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
私のテストでは約9の論理読み取り
3:ビューを使用せずにクエリを記述します。
もう1つの「解決策」は、ビューを使用せずにクエリを完全に記述することです。
4:ROW_NUMBER()
ビューに関数を保持せず、ビューへの呼び出しで関数を指定します。
この例は次のとおりです。
CREATE VIEW dbo.Bad2
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID;
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT ROW_NUMBER() OVER (PARTITION BY CodeMasterID ORDER BY CodeMasterID) AS Deal_HistoryID,* FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;
この問題を回避するには他にも工夫が必要です。重要な部分は、何が原因かを知ることです。
補遺#1
CREATE TABLE dbo.Codemaster
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Codemaster_History))
;
CREATE TABLE dbo.Deal
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
, SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL
, SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL
, PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Deal_History))
;
INSERT INTO dbo.Codemaster(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Deal_History(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster_History(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
GO
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
-- Very bad shame on you
補遺#2
CREATE TABLE dbo.Codemaster2
(
CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED
, ManagerID INT NULL
, ParentDeptID int NULL
);
CREATE TABLE dbo.Deal2
(
DealID int NOT NULL PRIMARY KEY CLUSTERED
, CodeMasterID INT NULL
, EvenMoreBlaID int NULL
);
INSERT INTO dbo.Codemaster2(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
INSERT INTO dbo.Deal2(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;
CREATE INDEX IX_CodeMasterID
ON dbo.Deal2(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster2(CodeMasterId);
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterId) AS Deal_HistoryID,
cm.*, d.*
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;
-- Guud
GO
CREATE VIEW dbo.Bad2
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2 cm
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155