Elimina l'operatore di ricerca chiavi (cluster) che rallenta le prestazioni


16

Come posso eliminare un operatore Ricerca chiavi (cluster) nel mio piano di esecuzione?

La tabella tblQuotesha già un indice cluster ( QuoteIDattivo) e 27 indici non cluster, quindi sto cercando di non crearne più.

Ho inserito la colonna dell'indice cluster QuoteIDnella mia query, sperando che possa aiutare, ma purtroppo è sempre la stessa.

Piano di esecuzione qui .

O visualizzalo:

inserisci qui la descrizione dell'immagine

Questo è ciò che dice l'operatore Ricerca chiave:

inserisci qui la descrizione dell'immagine

Query:

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

Progetto esecutivo:

inserisci qui la descrizione dell'immagine


Le righe Stimate vs Actual mostrano una notevole differenza. Forse SQL sta scegliendo un piano negativo perché non ha i dati per fare una buona stima. Con quale frequenza aggiorni le tue statistiche?
RDFozz,

Risposte:


23

Le ricerche chiave di vari tipi si verificano quando il processore di query deve ottenere valori dalle colonne che non sono archiviate nell'indice utilizzato per individuare le righe richieste affinché la query restituisca risultati.

Prendiamo ad esempio il seguente codice, in cui stiamo creando una tabella con un singolo indice:

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

Inseriremo 1.000.000 di righe nella tabella in modo da avere alcuni dati con cui lavorare:

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

Ora interrogheremo i dati con l'opzione per visualizzare il piano di esecuzione "effettivo":

SELECT *
FROM dbo.Table1
WHERE Table1ID = 500000;

Il piano di query mostra:

inserisci qui la descrizione dell'immagine

La query esamina l' IX_Table1indice con cui trovare la riga Table1ID = 5000000poiché esaminare quell'indice è molto più veloce della scansione dell'intera tabella alla ricerca di quel valore. Tuttavia, per soddisfare i risultati della query, il processore di query deve anche trovare il valore per le altre colonne nella tabella; è qui che entra in gioco la "Ricerca RID". Cerca nella tabella l'ID riga (il RID nella ricerca RID) associato alla riga contenente il Table1IDvalore di 500000, ottenendo i valori dalla Table1Datacolonna. Se passi il mouse sul nodo "Ricerca RID" nel piano, vedrai questo:

inserisci qui la descrizione dell'immagine

"Elenco di output" contiene le colonne restituite dalla ricerca RID.

Una tabella con un indice cluster e un indice non cluster costituisce un esempio interessante. La tabella seguente ha tre colonne; ID che è la chiave di clustering, Datche viene indicizzata da un indice non cluster IX_Table, e una terza colonna, 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

Prendi questa query di esempio:

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

Stiamo chiedendo a SQL Server di restituire ogni colonna dalla tabella in cui la Datcolonna contiene la parola Test. Abbiamo un paio di scelte qui; possiamo guardare la tabella (cioè l'indice cluster) - ma ciò implicherebbe la scansione dell'intera cosa poiché la tabella è ordinata dalla IDcolonna, il che non ci dice nulla sulle righe che contengono Testnella Datcolonna. L'altra opzione (e quella scelta da SQL Server) consiste nel cercare nell'indice IX_Table1non cluster per trovare la riga in cui Dat = 'Test', tuttavia, poiché abbiamo bisogno anche della Othcolonna, SQL Server deve eseguire una ricerca nell'indice cluster utilizzando una "chiave Ricerca "operazione. Questo è il piano per questo:

inserisci qui la descrizione dell'immagine

Se modifichiamo l'indice non cluster in modo che includa la Othcolonna:

DROP INDEX IX_Table1
ON dbo.Table1;
GO

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

Quindi rieseguire la query:

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

Ora vediamo un singolo indice non cluster cercare poiché SQL Server deve semplicemente individuare la riga in cui Dat = 'Test'l' IX_Table1indice, che include il valore per Othe il valore per la IDcolonna (chiave primaria), che è automaticamente presente in ogni non- indice cluster. Il programma:

inserisci qui la descrizione dell'immagine


6

La ricerca delle chiavi è causata dal fatto che il motore ha scelto di utilizzare un indice che non contiene tutte le colonne che si sta tentando di recuperare. Quindi l'indice non copre le colonne nell'istruzione select e where.

Per eliminare la ricerca della chiave è necessario includere le colonne mancanti (le colonne nell'elenco Output della ricerca della chiave) = ProducerContactGuid, QuoteStatusID, PolicyTypeID e ProducerLocationID o un altro modo è forzare la query a utilizzare invece l'indice cluster.

Si noti che 27 indici non cluster su una tabella possono essere dannosi per le prestazioni. Quando si esegue un aggiornamento, inserire o eliminare, SQL Server deve aggiornare tutti gli indici. Questo lavoro extra può influire negativamente sulle prestazioni.


Si noti inoltre che troppi indici possono confondere la compilazione del piano di esecuzione e possono comportare anche selezioni non ottimali.
Sbilenco il

4

Hai dimenticato di menzionare il volume di dati coinvolti in questa query. Inoltre, perché stai inserendo in una tabella temporanea? Se è necessario solo visualizzarlo, non eseguire un'istruzione insert.

Ai fini di questa query, tblQuotesnon sono necessari 27 indici non cluster. Ha bisogno di 1 indice cluster e 5 indici non cluster o, forse 6 indexex non cluster.

Questa query vorrebbe indici su queste colonne:

QuoteStatusID
ProducerLocationID
ProducerContactGuid
EffectiveDate
LineGUID
OriginalQuoteGUID

Ho anche notato il seguente codice:

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

è NON Sargablecioè non può utilizzare gli indici.

Per fare in modo che il codice lo SARgablecambi in questo:

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

Per rispondere alla tua domanda principale, "perché stai ricevendo una chiave Cerca":

Stai ottenendo KEY Look upperché alcune delle colonne menzionate nella query non sono presenti in un indice di copertura.

Puoi google e studiare su Covering Indexo Include index.

Nel mio esempio supponiamo che tblQuotes.QuoteStatusID sia un indice non cluster, quindi posso anche coprire DisplayStatus. Dal momento che si desidera DisplayStatus nel gruppo di risultati. Qualsiasi colonna che non è presente in un indice ed è presente nel gruppo di risultati può essere coperta per evitare KEY Look Up or Bookmark lookup. Questo è un esempio di indice di copertura:

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

** Disclaimer: ** Ricorda sopra è solo il mio esempio DisplayStatus può essere coperto con altri non CI dopo l'analisi.

Allo stesso modo dovrai creare un indice e un indice di copertura sulle altre tabelle coinvolte nella query.

Stai Index SCANanche entrando nel tuo piano.

Ciò può accadere perché non vi è alcun indice sulla tabella o quando esiste un grande volume di dati, l'ottimizzatore può decidere di eseguire la scansione anziché eseguire una ricerca dell'indice.

Ciò può verificarsi anche a causa di High cardinality. Ottenere più numero di righe rispetto a quanto richiesto a causa di un join difettoso. Questo può anche essere corretto.

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.