Seleccionar * de Vista toma 4 minutos

11

Me encuentro con un problema en el que cuando ejecuto una consulta en una vista tarda más de 4 minutos. Sin embargo, cuando ejecuto las tripas de la consulta, finaliza en aproximadamente 1 segundo.

Lo único de lo que no estoy seguro es que las tablas que se unen son ambas tablas temporales.

Plan de consulta ad hoc: https://www.brentozar.com/pastetheplan/?id=BykohB2p4

Ver plan de consultas: https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E

¿Alguna sugerencia sobre dónde tratar de resolver esto?

Ver código:

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

Se agregó Partición por y se obtienen resultados similares a la consulta ad hoc.

usuario761786
fuente

Respuestas:

18

Las principales diferencias de rendimiento

Las principales diferencias aquí son que la consulta de mejor rendimiento es presionar hacia abajo el predicado de búsqueda CodeMasterIDen las 4 tablas (2 tablas temporales (real e historial)) donde la selección en la vista parece no hacerlo hasta el final (operador de filtro) .

TL DR;

El problema se debe a que los parámetros no empujan a las funciones de la ventana en ciertos casos, como las vistas. La solución más fácil es agregar OPTION(RECOMPILE)a la llamada de vista para que el optimizador 'vea' los parámetros en tiempo de ejecución si es posible. Si es demasiado costoso recompilar el plan de ejecución para cada llamada de consulta, usar una función con valores de tabla en línea que espera que un parámetro pueda ser una solución. Hay un excelente Blogpost de Paul White sobre esto. Para una forma más detallada de encontrar y resolver su problema particular, siga leyendo.


La consulta de mejor desempeño

Tabla Codemaster

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Tabla de ofertas

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Me encanta el olor de buscar predicados en la mañana


La gran mala consulta

Tabla Codemaster

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Esta es una zona de solo predicado

La mesa de Deal

ingrese la descripción de la imagen aquí

Pero el optimizador no leyó "El arte del trato ™"

ingrese la descripción de la imagen aquí

... y no aprende del pasado

Hasta que todos esos datos lleguen al operador del filtro

ingrese la descripción de la imagen aquí


Entonces, ¿qué da?

El principal problema aquí es que el optimizador no 've' los parámetros en tiempo de ejecución debido a las funciones de la ventana en la vista y no puede usar el SelOnSeqPrj (seleccione el proyecto de secuencia, más abajo en esta publicación como referencia) .

Pude replicar los mismos resultados con una muestra de prueba y usar SP_EXECUTESQLpara parametrizar la llamada a la vista. Ver anexo para el DDL / DML

ejecutar una consulta en una vista de prueba con una función de ventana y un INNER JOIN

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

Resultando en aproximadamente 4.5s de tiempo de CPU y 3.2s de tiempo transcurrido

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

Cuando agregamos el dulce abrazo de 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; 

Todo está bién.

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

Por qué

Todo esto nuevamente respalda el punto de no poder aplicar el @P1predicado a las tablas debido a la función de ventana y la parametrización que resulta en el operador de filtro

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

No solo es un problema para las tablas temporales

Ver anexo 2

Incluso cuando no se usan tablas temporales, esto sucede: ingrese la descripción de la imagen aquí

Se ve el mismo resultado al escribir la consulta de esta manera:

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

Nuevamente, el optimizador no está presionando el predicado antes de aplicar la función de ventana.

Al omitir el 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;

Todo está bien

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.

Entonces, ¿dónde nos deja todo eso?

Se ROW_NUMBER()calcula antes de que se aplique el filtro en las consultas incorrectas.

Y todo esto nos lleva a esta publicación de blog de 2013 de Paul White sobre funciones y vistas de ventanas.

Una de las partes importantes para nuestro ejemplo es esta declaración:

Desafortunadamente, la regla de simplificación SelOnSeqPrj solo funciona cuando el predicado realiza una comparación con una constante. Por esa razón, la siguiente consulta produce el plan subóptimo en SQL Server 2008 y versiones posteriores:

DECLARE @ProductID INT = 878;

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

ingrese la descripción de la imagen aquí

Esta parte corresponde a lo que hemos visto al declarar el parámetro nosotros mismos / usar SP_EXECUTESQLen la vista.


Las soluciones reales

1: OPCIÓN (RECOMPILAR)

Sabemos que OPTION(RECOMPILE)'ver' el valor en tiempo de ejecución es una posibilidad. Cuando recompilar el plan de ejecución para cada llamada de consulta es demasiado costoso, existen otras soluciones.

2: función de valor de tabla en línea con un parámetro

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

Resultando en los predicados de búsqueda esperados

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

Con aproximadamente 9 lecturas lógicas en mi prueba

3: Escribir la consulta sin el uso de una vista.

La otra 'solución' podría ser escribir la consulta por completo sin el uso de una vista.

4: No mantener la ROW_NUMBER()función en la vista, sino especificarla en la llamada a la vista.

Un ejemplo de esto sería:

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;

Debería haber otras formas creativas de solucionar este problema, la parte importante es saber qué lo causa.


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

Anexo # 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
fuente