Seleziona * da Visualizza richiede 4 minuti


11

Sto riscontrando un problema in cui quando eseguo una query su una vista ci vogliono più di 4 minuti. Tuttavia, quando eseguo il coraggio della query termina in circa 1 secondo.

L'unica cosa di cui non sono sicuro è che le tabelle che vengono unite sono entrambe tabelle temporali.

Piano di query ad hoc: https://www.brentozar.com/pastetheplan/?id=BykohB2p4

Visualizza il piano delle query: https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E

Qualche suggerimento su dove provare a capirlo?

Visualizza codice:

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

Aggiunta la partizione per e risultati simili alla query ad hoc.

Risposte:


18

Le principali differenze di prestazioni

Le differenze principali qui sono che la query con le prestazioni migliori sta spingendo verso il basso il predicato di ricerca CodeMasterIDsu tutte e 4 le tabelle (2 tabelle temporali (effettive e cronologiche)) in cui la selezione nella vista sembra non farlo fino alla fine (operatore filtro) .

TL DR;

Il problema è dovuto ai parametri che non spingono verso il basso le funzioni della finestra in alcuni casi come le viste. La soluzione più semplice è quella di aggiungere OPTION(RECOMPILE)alla chiamata di visualizzazione in modo che l'ottimizzatore 'veda' i parametri in fase di esecuzione se questa è una possibilità. Se è troppo costoso ricompilare il piano di esecuzione per ogni chiamata di query, l'utilizzo di una funzione con valori di tabella in linea che prevede un parametro potrebbe essere una soluzione. C'è un eccellente Blogpost di Paul White su questo. Per un modo più dettagliato su come trovare e risolvere il tuo problema specifico, continua a leggere.


La query con le migliori prestazioni

Tavolo Codemaster

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

Tabella delle offerte

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

Adoro l'odore del cercare predicati al mattino


La grande brutta domanda

Tavolo Codemaster

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

Questa è una zona solo predicato

La tabella delle offerte

inserisci qui la descrizione dell'immagine

Ma l'ottimizzatore non ha letto "The art of the deal ™"

inserisci qui la descrizione dell'immagine

... e non impara dal passato

Fino a quando tutti quei dati non raggiungono l'operatore del filtro

inserisci qui la descrizione dell'immagine


Quindi, cosa dà?

Il problema principale qui è che l'ottimizzatore non "vede" i parametri in fase di esecuzione a causa delle funzioni della finestra nella vista e non è in grado di utilizzare il SelOnSeqPrj (selezionare il progetto sequenza, più in basso in questo post per riferimento) .

Sono stato in grado di replicare gli stessi risultati con un campione di prova e usando SP_EXECUTESQLper parametrizzare la chiamata alla vista. Vedi addendum per DDL / DML

esecuzione di una query su una vista di prova con una funzione finestra e un INNER JOIN

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

Il risultato è circa 4,5 secondi di tempo di CPU e 3,2 secondi di tempo trascorso

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

Quando aggiungiamo il dolce abbraccio di 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; 

È tutto buono.

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

Perché

Tutto ciò supporta ancora una volta il punto di non essere in grado di applicare il @P1predicato alle tabelle a causa della funzione finestra e della parametrizzazione risultanti nell'operatore filtro

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Non solo un problema per le tabelle temporali

Vedi addendum 2

Anche quando non si usano le tabelle temporali, ciò accade: inserisci qui la descrizione dell'immagine

Lo stesso risultato si vede scrivendo la query in questo modo:

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

Ancora una volta, l'ottimizzatore non sta spingendo verso il basso il predicato prima di applicare la funzione finestra.

Quando si omette 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;

Tutto bene

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.

quindi dove ci lascia tutto ciò?

Il ROW_NUMBER()è calcolato prima che il filtro viene applicato sui cattivi query.

E tutto ciò ci porta a questo blog post del 2013 di Paul White su funzioni e viste delle finestre.

Una delle parti importanti per il nostro esempio è questa affermazione:

Sfortunatamente, la regola di semplificazione SelOnSeqPrj funziona solo quando il predicato esegue un confronto con una costante. Per tale motivo, la query seguente produce il piano non ottimale su SQL Server 2008 e versioni successive:

DECLARE @ProductID INT = 878;

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

inserisci qui la descrizione dell'immagine

Questa parte corrisponde a ciò che abbiamo visto dichiarando noi stessi il parametro / usando SP_EXECUTESQLnella vista.


Le soluzioni attuali

1: OPZIONE (RICOMPILE)

Sappiamo che OPTION(RECOMPILE)'vedere' il valore in fase di esecuzione è una possibilità. Quando la ricompilazione del piano di esecuzione per ciascuna chiamata di query è troppo costosa, esistono altre soluzioni.

2: Funzione con valori di tabella incorporata con un parametro

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

Il risultato nei predicati di ricerca previsti

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

Con circa 9 letture logiche sul mio test

3: Scrivere la query senza l'uso di una vista.

L'altra 'soluzione' potrebbe essere la scrittura della query interamente senza l'uso di una vista.

4: Non mantenere il ROW_NUMBER() funzione nella vista, ma specificandola nella chiamata alla vista.

Un esempio di questo sarebbe:

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;

Dovrebbero esserci altri modi creativi per aggirare questo problema, la parte importante è sapere cosa lo causa.


Addendum # 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

Addendum # 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.