Eliminar o operador Key Lookup (Clustered) que diminui o desempenho

16

Como posso eliminar um operador Key Lookup (Clustered) no meu plano de execução?

A tabela tblQuotesjá possui um índice clusterizado ( QuoteIDativado) e 27 índices não clusterizados, portanto, estou tentando não criar mais.

Coloquei a coluna de índice clusterizado QuoteIDna minha consulta, esperando que isso ajude - mas infelizmente ainda é o mesmo.

Plano de execução aqui .

Ou veja:

insira a descrição da imagem aqui

É o que o operador Key Lookup diz:

insira a descrição da imagem aqui

Inquerir:

declare
        @EffDateFrom datetime ='2017-02-01',
        @EffDateTo   datetime ='2017-08-28'

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

IF OBJECT_ID('tempdb..#Data') IS NOT NULL
    DROP TABLE #Data 
CREATE TABLE #Data
(
    QuoteID int NOT NULL,   --clustered index

    [EffectiveDate] [datetime] NULL, --not indexed
    [Submitted] [int] NULL,
    [Quoted] [int] NULL,
    [Bound] [int] NULL,
    [Exonerated] [int] NULL,
    [ProducerLocationId] [int] NULL,
    [ProducerName] [varchar](300) NULL,
    [BusinessType] [varchar](50) NULL,
    [DisplayStatus] [varchar](50) NULL,
    [Agent] [varchar] (50) NULL,
    [ProducerContactGuid] uniqueidentifier NULL
)
INSERT INTO #Data
    SELECT 
        tblQuotes.QuoteID,

          tblQuotes.EffectiveDate,
          CASE WHEN lstQuoteStatus.QuoteStatusID >= 1   THEN 1 ELSE 0 END AS Submitted,
          CASE WHEN lstQuoteStatus.QuoteStatusID = 2 or lstQuoteStatus.QuoteStatusID = 3 or lstQuoteStatus.QuoteStatusID = 202 THEN 1 ELSE 0 END AS Quoted,
          CASE WHEN lstQuoteStatus.Bound = 1 THEN 1 ELSE 0 END AS Bound,
          CASE WHEN lstQuoteStatus.QuoteStatusID = 3 THEN 1 ELSE 0 END AS Exonareted,
          tblQuotes.ProducerLocationID,
          P.Name + ' / '+ P.City as [ProducerName], 
        CASE WHEN tblQuotes.PolicyTypeID = 1 THEN 'New Business' 
             WHEN tblQuotes.PolicyTypeID = 3 THEN 'Rewrite'
             END AS BusinessType,
        tblQuotes.DisplayStatus,
        tblProducerContacts.FName +' '+ tblProducerContacts.LName as Agent,
        tblProducerContacts.ProducerContactGUID
FROM    tblQuotes 
            INNER JOIN lstQuoteStatus 
                on tblQuotes.QuoteStatusID=lstQuoteStatus.QuoteStatusID
            INNER JOIN tblProducerLocations P 
                On P.ProducerLocationID=tblQuotes.ProducerLocationID
            INNER JOIN tblProducerContacts 
                ON dbo.tblQuotes.ProducerContactGuid = tblProducerContacts.ProducerContactGUID

WHERE   DATEDIFF(D,@EffDateFrom,tblQuotes.EffectiveDate)>=0 AND DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <=0
        AND dbo.tblQuotes.LineGUID = '6E00868B-FFC3-4CA0-876F-CC258F1ED22D'--Surety
        AND tblQuotes.OriginalQuoteGUID is null

select * from #Data

Plano de execução:

insira a descrição da imagem aqui

Serdia
fonte
As linhas Estimativa x Real mostram uma diferença notável. Talvez o SQL esteja escolhendo um plano ruim porque não possui os dados para fazer uma boa estimativa. Com que frequência você atualiza suas estatísticas?
RDFozz

Respostas:

23

As principais pesquisas de vários tipos ocorrem quando o processador de consultas precisa obter valores de colunas que não são armazenadas no índice usado para localizar as linhas necessárias para a consulta retornar resultados.

Tomemos, por exemplo, o código a seguir, onde estamos criando uma tabela com um único índice:

USE tempdb;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO

CREATE TABLE dbo.Table1
(
    Table1ID int NOT NULL IDENTITY(1,1)
    , Table1Data nvarchar(30) NOT NULL
);

CREATE INDEX IX_Table1
ON dbo.Table1 (Table1ID);
GO

Vamos inserir 1.000.000 de linhas na tabela para termos alguns dados com os quais trabalhar:

INSERT INTO dbo.Table1 (Table1Data)
SELECT TOP(1000000) LEFT(c.name, 30)
FROM sys.columns c
    CROSS JOIN sys.columns c1
    CROSS JOIN sys.columns c2;
GO

Agora, consultaremos os dados com a opção de exibir o plano de execução "real":

SELECT *
FROM dbo.Table1
WHERE Table1ID = 500000;

O plano de consulta mostra:

insira a descrição da imagem aqui

A consulta examina o IX_Table1índice para encontrar a linha, Table1ID = 5000000pois a análise desse índice é muito mais rápida do que a varredura de toda a tabela procurando esse valor. No entanto, para satisfazer os resultados da consulta, o processador de consultas também deve encontrar o valor para as outras colunas na tabela; é aqui que entra a "Pesquisa de RID". Ele procura na tabela o ID da linha (o RID na pesquisa de RID) associado à linha que contém o Table1IDvalor de 500000, obtendo os valores da Table1Datacoluna. Se você passar o mouse sobre o nó "Pesquisa de RID" no plano, verá o seguinte:

insira a descrição da imagem aqui

A "Lista de saída" contém as colunas retornadas pela pesquisa do RID.

Uma tabela com um índice clusterizado e um índice não clusterizado é um exemplo interessante. A tabela abaixo possui três colunas; ID que é a chave de cluster, Datque é indexada por um índice não em cluster IX_Tablee uma terceira coluna Oth.

USE tempdb;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO

CREATE TABLE dbo.Table1
(
    ID int NOT NULL IDENTITY(1,1) 
        PRIMARY KEY CLUSTERED
    , Dat nvarchar(30) NOT NULL
    , Oth nvarchar(3) NOT NULL
);

CREATE INDEX IX_Table1
ON dbo.Table1 (Dat);
GO

INSERT INTO dbo.Table1 (Dat, Oth)
SELECT TOP(1000000) CRYPT_GEN_RANDOM(30), CRYPT_GEN_RANDOM(3)
FROM sys.columns c
    CROSS JOIN sys.columns c1
    CROSS JOIN sys.columns c2;
GO

Pegue esta consulta de exemplo:

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';

Pedimos ao SQL Server para retornar todas as colunas da tabela em que a Datcoluna contém a palavra Test. Temos algumas opções aqui; podemos olhar para a tabela (ou seja, o índice clusterizado) - mas isso exigiria a varredura de toda a coisa, já que a tabela é ordenada pela IDcoluna, o que não nos diz nada sobre quais linhas contêm Testna Datcoluna. A outra opção (e a escolhida pelo SQL Server) consiste em procurar no IX_Table1índice não agrupado em cluster para encontrar a linha em que Dat = 'Test', no entanto, como também precisamos da Othcoluna, o SQL Server deve executar uma pesquisa no índice agrupado usando uma "Chave Pesquisa ". Este é o plano para isso:

insira a descrição da imagem aqui

Se modificarmos o índice não agrupado para que inclua a Othcoluna:

DROP INDEX IX_Table1
ON dbo.Table1;
GO

CREATE INDEX IX_Table1
ON dbo.Table1 (Dat)
INCLUDE (Oth);        <---- This is the only change
GO

Em seguida, execute novamente a consulta:

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';

Agora vemos uma única busca de índice não clusterizado, pois o SQL Server simplesmente precisa localizar a linha Dat = 'Test'em que lugar do IX_Table1índice, que inclui o valor para Othe o valor da IDcoluna (a chave primária), presente automaticamente em todos os índice agrupado. O plano:

insira a descrição da imagem aqui

Max Vernon
fonte
6

A pesquisa de chave é causada porque o mecanismo optou por usar um índice que não contém todas as colunas que você está tentando buscar. Portanto, o índice não está cobrindo as colunas na instrução select e where.

Para eliminar a pesquisa de chave, é necessário incluir as colunas ausentes (as colunas na lista Saída da pesquisa de chave) = ProducerContactGuid, QuoteStatusID, PolicyTypeID e ProducerLocationID ou outra maneira é forçar a consulta a usar o índice clusterizado.

Observe que 27 índices não agrupados em uma tabela podem prejudicar o desempenho. Ao executar uma atualização, inserir ou excluir, o SQL Server deve atualizar todos os índices. Esse trabalho extra pode afetar negativamente o desempenho.

Daniel Björk
fonte
Observe também que muitos índices podem confundir a compilação do plano de execução e também podem resultar em seleções abaixo do ideal.
Disparinado
4

Você esqueceu de mencionar o volume de dados envolvidos nesta consulta. Além disso, por que você está inserindo em uma tabela temporária? Se você precisar apenas exibir, não execute uma instrução de inserção.

Para os fins desta consulta, tblQuotesnão é necessário 27 índices não agrupados em cluster. Ele precisa de 1 índice agrupado e 5 índices não agrupados ou, talvez, 6 índices não agrupados.

Esta consulta gostaria de índices nessas colunas:

QuoteStatusID
ProducerLocationID
ProducerContactGuid
EffectiveDate
LineGUID
OriginalQuoteGUID

Também notei o seguinte código:

DATEDIFF(D, @EffDateFrom, tblQuotes.EffectiveDate) >= 0 AND 
DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <= 0

é NON Sargableou seja, não pode utilizar índices.

Para fazer esse código, SARgablemude para:

tblQuotes.EffectiveDate >= @EffDateFrom 
AND  tblQuotes.EffectiveDate <= @EffDateFrom

Para responder à sua pergunta principal, "por que você está recebendo uma consulta importante":

Você está recebendo KEY Look upporque algumas das colunas mencionadas na consulta não estão presentes em um índice de cobertura.

Você pode pesquisar no Google e estudar sobre Covering Indexou Include index.

No meu exemplo, suponha que tblQuotes.QuoteStatusID é um índice não clusterizado, também posso cobrir o DisplayStatus. Desde que você deseja DisplayStatus no conjunto de resultados. Qualquer coluna que não esteja presente em um índice e esteja presente no conjunto de resultados pode ser coberta para evitar KEY Look Up or Bookmark lookup. Este é um exemplo de cobertura do índice:

create nonclustered index tblQuotes_QuoteStatusID 
on tblQuotes(QuoteStatusID)
include(DisplayStatus);

** Isenção de responsabilidade: ** Lembre-se de que acima é apenas meu exemplo que o DisplayStatus pode ser coberto com outros Não ICs após a análise.

Da mesma forma, você precisará criar um índice e um índice de cobertura nas outras tabelas envolvidas na consulta.

Você Index SCANtambém está recebendo seu plano.

Isso pode acontecer porque não há índice na tabela ou quando há um grande volume de dados que o otimizador pode decidir varrer em vez de executar uma busca de índice.

Isso também pode ocorrer devido a High cardinality. Obtendo mais número de linhas do que o necessário devido à associação incorreta. Isso também pode ser corrigido.

KumarHarsh
fonte