Selecionar * em Exibir leva 4 minutos

11

Estou correndo para um problema em que, quando executo uma consulta em uma exibição, leva mais de 4 minutos. No entanto, quando executo a coragem da consulta, ela termina em 1 segundo.

A única coisa que não tenho certeza é que as tabelas que estão sendo unidas são ambas tabelas temporais.

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

Exibir plano de consulta: https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E

Alguma sugestão sobre onde tentar descobrir isso?

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

Adicionou a Partição por e obteve resultados semelhantes à consulta ad hoc.

user761786
fonte

Respostas:

18

As principais diferenças de desempenho

As principais diferenças aqui são que a consulta com melhor desempenho está pressionando o predicado de busca CodeMasterIDem todas as 4 tabelas (2 tabelas temporais (real e histórico)) em que a seleção na exibição parece não fazer isso até o final (operador de filtro) .

TL DR;

O problema ocorre devido aos parâmetros que não pressionam para baixo as funções da janela em certos casos, como visualizações. A solução mais fácil é adicionar OPTION(RECOMPILE)a chamada de visualização para fazer o otimizador 'ver' os parâmetros em tempo de execução, se for possível. Se for muito caro recompilar o plano de execução para cada chamada de consulta, usar uma função com valor de tabela embutida que espera que um parâmetro possa ser uma solução. Há um excelente Blogpost de Paul White sobre isso. Para uma maneira mais detalhada de encontrar e resolver seu problema específico, continue lendo.


A consulta com melhor desempenho

Tabela Codemaster

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Tabela de ofertas

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Eu amo o cheiro de procurar predicados pela manhã


A grande consulta ruim

Tabela Codemaster

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Esta é uma zona apenas de predicado

A tabela Deal

insira a descrição da imagem aqui

Mas o otimizador não leu 'The art of the deal ™ "

insira a descrição da imagem aqui

... e não aprende com o passado

Até que todos esses dados cheguem ao operador do filtro

insira a descrição da imagem aqui


Então, o que dá?

O principal problema aqui é o otimizador não 'vendo' os parâmetros em tempo de execução devido às funções da janela na exibição e não podendo usá-lo SelOnSeqPrj (selecione no projeto de sequência, mais abaixo neste post para referência) .

Consegui replicar os mesmos resultados com uma amostra de teste e usando SP_EXECUTESQLpara parametrizar a chamada para a exibição. Consulte o adendo para o DDL / DML

executando uma consulta em uma exibição de teste com uma função de janela e um INNER JOIN

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

Resultando em cerca de 4,5s de tempo da CPU e 3,2s de tempo decorrido

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

Quando adicionamos o doce abraço 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; 

Esta tudo bem.

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

Por quê

Isso tudo novamente suporta o ponto de não poder aplicar o @P1predicado às tabelas devido à função da janela e à parametrização que resultam no operador de filtro

insira a descrição da imagem aqui insira a descrição da imagem aqui

Não é apenas um problema para tabelas temporais

Ver adendo 2

Mesmo quando não estiver usando tabelas temporais, isso acontece: insira a descrição da imagem aqui

O mesmo resultado é visto ao escrever a consulta assim:

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

Novamente, o otimizador não está pressionando o predicado antes de aplicar a função da janela.

Ao omitir o 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;

Tudo está bem

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.

então onde tudo isso nos deixa?

O ROW_NUMBER()é calculado antes que o filtro seja aplicado nas consultas incorretas.

E tudo isso nos leva a este post de 2013 de Paul White sobre funções e visualizações da janela.

Uma das partes importantes para o nosso exemplo é esta afirmação:

Infelizmente, a regra de simplificação SelOnSeqPrj funciona apenas quando o predicado executa uma comparação com uma constante. Por esse motivo, a consulta a seguir produz o plano abaixo do ideal no SQL Server 2008 e posterior:

DECLARE @ProductID INT = 878;

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

insira a descrição da imagem aqui

Esta parte corresponde ao que vimos ao declarar o parâmetro por nós mesmos / usando SP_EXECUTESQLna exibição.


As soluções reais

1: OPÇÃO (RECOMPLE)

Sabemos que OPTION(RECOMPILE)"ver" o valor em tempo de execução é uma possibilidade. Ao recompilar o plano de execução para cada chamada de consulta é muito caro, existem outras soluções.

2: Função com valor de tabela embutida com um 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 nos predicados de busca esperados

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

Com cerca de 9 leituras lógicas no meu teste

3: Escrevendo a consulta sem o uso de uma exibição.

A outra 'solução' pode estar gravando a consulta inteiramente sem o uso de uma exibição.

4: Não mantendo a ROW_NUMBER()função na visualização, mas especificando-a na chamada para a visualização.

Um exemplo disso seria:

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;

Deve haver outras maneiras criativas de contornar esse problema, a parte importante é saber o que o causa.


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

Adendo # 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
fonte