Die Auswahl von * in der Ansicht dauert 4 Minuten

11

Ich stoße auf ein Problem, bei dem das Ausführen einer Abfrage für eine Ansicht mehr als 4 Minuten dauert. Wenn ich jedoch den Mut der Abfrage ausführe, endet sie in etwa 1 Sekunde.

Das einzige, bei dem ich mir nicht sicher bin, ist, dass die Tabellen, die verbunden werden, beide temporale Tabellen sind.

Ad-hoc-Abfrageplan: https://www.brentozar.com/pastetheplan/?id=BykohB2p4

Abfrageplan anzeigen: https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E

Irgendwelche Vorschläge, wo Sie versuchen können, dies herauszufinden?

Code anzeigen:

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

Partition von hinzugefügt und ähnliche Ergebnisse wie bei der Ad-hoc-Abfrage erhalten.

user761786
quelle

Antworten:

18

Die wichtigsten Leistungsunterschiede

Die Hauptunterschiede bestehen darin, dass die Abfrage mit der besseren Leistung das Suchprädikat CodeMasterIDfür alle 4 Tabellen (2 temporale Tabellen (Ist & Verlauf)) nach unten drückt, wobei die Auswahl in der Ansicht dies bis zum Ende nicht zu tun scheint (Filteroperator) .

TL DR;

Das Problem ist darauf zurückzuführen, dass Parameter in bestimmten Fällen, z. B. in Ansichten, nicht auf Fensterfunktionen übertragen werden. Die einfachste Lösung besteht darin OPTION(RECOMPILE), den Aufruf der Ansicht zu erweitern, damit der Optimierer die Parameter zur Laufzeit "sieht", wenn dies möglich ist. Wenn es zu teuer ist, den Ausführungsplan für jeden Abfrageaufruf neu zu kompilieren, kann die Verwendung einer Inline-Tabellenwertfunktion, die einen Parameter erwartet, eine Lösung sein. Es gibt einen ausgezeichneten Blogpost von Paul White dazu. Lesen Sie weiter, um eine detailliertere Methode zum Auffinden und Lösen Ihres speziellen Problems zu erhalten.


Die leistungsstärkere Abfrage

Codemaster-Tabelle

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Deal Tabelle

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Ich liebe den Geruch von Suchprädikaten am Morgen


Die große schlechte Frage

Codemaster-Tabelle

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Dies ist eine Zone nur für Prädikate

Die Deal-Tabelle

Geben Sie hier die Bildbeschreibung ein

Der Optimierer hat jedoch nicht "The art of the deal ™" gelesen.

Geben Sie hier die Bildbeschreibung ein

... und lernt nicht aus der Vergangenheit

Bis all diese Daten den Filteroperator erreichen

Geben Sie hier die Bildbeschreibung ein


Also, was gibt es?

Das Hauptproblem hierbei ist, dass das Optimierungsprogramm die Parameter zur Laufzeit aufgrund der Fensterfunktionen in der Ansicht nicht "sieht" und das nicht verwenden kann SelOnSeqPrj (Auswahl eines Sequenzprojekts weiter unten in diesem Beitrag als Referenz) .

Ich konnte dieselben Ergebnisse mit einem Testmuster replizieren und SP_EXECUTESQLden Aufruf der Ansicht parametrisieren. Siehe Anhang zur DDL / DML

Ausführen einer Abfrage für eine Testansicht mit einer Fensterfunktion und einem INNER JOIN

SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;

Dies führte zu einer CPU-Zeit von ca. 4,5 s und einer verstrichenen Zeit von 3,2 s

 SQL Server Execution Times:
   CPU time = 4595 ms,  elapsed time = 3209 ms.

Wenn wir die süße Umarmung von hinzufügen 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; 

Es ist alles gut.

 SQL Server Execution Times:
   CPU time = 16 ms,  elapsed time = 98 ms.

Warum

Dies alles unterstützt wiederum den Punkt, dass das @P1Prädikat aufgrund der Fensterfunktion und Parametrisierung , die zum Filteroperator führen, nicht auf die Tabellen angewendet werden kann

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

Nicht nur ein Problem für Zeittabellen

Siehe Anhang 2

Auch wenn keine Zeittabellen verwendet werden, geschieht Folgendes: Geben Sie hier die Bildbeschreibung ein

Das gleiche Ergebnis wird beim Schreiben der Abfrage wie folgt angezeigt:

DECLARE @P1 int = 37155
SELECT * FROM  dbo.Bad2
Where CodeMasterID = @P1;

Auch hier drückt der Optimierer das Prädikat nicht nach unten, bevor die Fensterfunktion angewendet wird.

Wenn Sie die ROW_NUMBER () weglassen

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;

Alles ist gut

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.

Wo bleibt uns das alles?

Der ROW_NUMBER()wird berechnet, bevor der Filter auf die fehlerhaften Abfragen angewendet wird.

Und all dies führt uns zu diesem Blogpost von Paul White aus dem Jahr 2013 über Fensterfunktionen und -ansichten.

Einer der wichtigen Teile für unser Beispiel ist diese Aussage:

Leider funktioniert die SelOnSeqPrj-Vereinfachungsregel nur, wenn das Prädikat einen Vergleich mit einer Konstanten durchführt. Aus diesem Grund erzeugt die folgende Abfrage den suboptimalen Plan unter SQL Server 2008 und höher:

DECLARE @ProductID INT = 878;

SELECT
    mrt.ProductID,
    mrt.TransactionID,
    mrt.ReferenceOrderID,
    mrt.TransactionDate,
    mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt 
WHERE
    mrt.ProductID = @ProductID;

Geben Sie hier die Bildbeschreibung ein

Dieser Teil entspricht dem, was wir gesehen haben, als wir den Parameter selbst deklariert / SP_EXECUTESQLin der Ansicht verwendet haben.


Die tatsächlichen Lösungen

1: OPTION (RECOMPILE)

Wir wissen, dass OPTION(RECOMPILE)es eine Möglichkeit ist, den Wert zur Laufzeit zu sehen. Wenn das Neukompilieren des Ausführungsplans für jeden Abfrageaufruf zu teuer ist, gibt es andere Lösungen.

2: Inline-Tabellenwertfunktion mit einem Parameter

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

Daraus resultieren die erwarteten Suchprädikate

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 0 ms.

Mit ungefähr 9 logischen Lesevorgängen bei meinem Test

3: Schreiben der Abfrage ohne Verwendung einer Ansicht.

Die andere "Lösung" könnte darin bestehen, die Abfrage vollständig ohne Verwendung einer Ansicht zu schreiben.

4: Die ROW_NUMBER()Funktion nicht in der Ansicht behalten , sondern im Aufruf der Ansicht angeben.

Ein Beispiel hierfür wäre:

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;

Es sollte andere kreative Wege geben, um dieses Problem zu umgehen. Der wichtige Teil ist zu wissen, was es verursacht.


Nachtrag Nr. 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

Nachtrag Nr. 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
Randi Vertongen
quelle