ビューから*を選択すると4分かかります


11

ビューに対してクエリを実行すると4分以上かかるという問題が発生しています。しかし、クエリの根本を実行すると、1秒程度で終了します。

唯一わからないのは、結合されるテーブルが両方ともテンポラルテーブルであることです。

アドホッククエリプラン:https : //www.brentozar.com/pastetheplan/?id=BykohB2p4

クエリプランを表示:https : //www.brentozar.com/pastetheplan/?id=SkIfTHh6E

どこで試してこれを理解するかについての提案はありますか?

コードを表示:

ALTER VIEW [dbo].[vwDealHistoryPITA]
AS
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.LastUpdateDate) AS Deal_HistoryID,
       cm.CodeMasterID,
       cm.ProjectName,
       cm.[Status],
       d.CompanyID,
       d.DealTypeMasterID,
       cm.[Description],
       d.PassiveInd,
       d.ApproxTPGOwnership,
       d.NumberBoardSeats,
       d.FollowonInvestmentInd,
       d.SocialImpactInd,
       d.EquityInd,
       d.DebtInd,
       d.RealEstateInd,
       d.TargetPctgReturn,
       d.ApproxTotalDealSize,
       cm.CurrencyCode,
       d.ConflictCheck,
       cm.CreatedDate,
       cm.CreatedBy,
       cm.LastUpdateDate,
       cm.LastUpdateBy,
       d.ExpensesExceedThresholdDate,
       d.CurrentTPGCheckSize,
       d.PreferredEquityInd,
       d.ConvertibleDebtInd,
       d.OtherRealAssetsInd,
       d.InitialTPGCheckSize,
       d.DirectLendingInd,
       cm.NameApproved,
       cm.FolderID,
       cm.CodaProcessedDateTime,
       cm.DeadDate,
       d.SectorMasterID,
       d.DTODataCompleteDate,
       cm.ValidFrom AS CodeMasterValidFrom,
       cm.ValidTo   AS CodeMasterValidTo,
       d.validFrom  AS DealValidFrom,
       d.validTo    AS DealValidTo
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm 
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID;
GO

パーティションを追加し、アドホッククエリと同様の結果を取得します。

回答:


18

主なパフォーマンスの違い

ここでの主な違いは、より良い実行するクエリが押し下げされた上で述語を求めることをしている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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.