Beseitigen Sie den Key Lookup (Clustered) -Operator, der die Leistung verlangsamt


16

Wie kann ich einen Key Lookup (Clustered) -Operator in meinem Ausführungsplan entfernen?

Die Tabelle hat tblQuotesbereits einen Clustered-Index (on QuoteID) und 27 Nonclustered-Indizes, daher versuche ich, keine weiteren zu erstellen.

Ich habe die Clustered-Index-Spalte QuoteIDin meine Abfrage eingefügt, in der Hoffnung, dass sie hilft - aber leider immer noch dieselbe.

Ausführungsplan hier .

Oder sehen Sie es sich an:

Geben Sie hier die Bildbeschreibung ein

Dies sagt der Key Lookup-Operator:

Geben Sie hier die Bildbeschreibung ein

Abfrage:

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

Ausführungsplan:

Geben Sie hier die Bildbeschreibung ein


Die Zeilen "Geschätzt" und "Ist" zeigen einen bemerkenswerten Unterschied. Vielleicht wählt SQL einen schlechten Plan aus, weil es nicht die Daten hat, um eine gute Schätzung vorzunehmen. Wie oft aktualisieren Sie Ihre Statistiken?
RDFozz

Antworten:


23

Wichtige Suchvorgänge verschiedener Varianten treten auf, wenn der Abfrageprozessor Werte aus Spalten abrufen muss, die nicht im Index gespeichert sind, der zum Auffinden der Zeilen verwendet wird, die für die Abfrage erforderlich sind, um Ergebnisse zurückzugeben.

Nehmen Sie zum Beispiel den folgenden Code, in dem wir eine Tabelle mit einem einzelnen Index erstellen:

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

Wir werden 1.000.000 Zeilen in die Tabelle einfügen, damit wir mit einigen Daten arbeiten können:

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

Jetzt werden wir die Daten mit der Option abfragen, den "tatsächlichen" Ausführungsplan anzuzeigen:

SELECT *
FROM dbo.Table1
WHERE Table1ID = 500000;

Der Abfrageplan zeigt:

Geben Sie hier die Bildbeschreibung ein

Die Abfrage untersucht den IX_Table1Index, um die Zeile zu finden, Table1ID = 5000000da das Betrachten dieses Index viel schneller ist als das Durchsuchen der gesamten Tabelle nach diesem Wert. Um die Abfrageergebnisse zu erfüllen, muss der Abfrageprozessor jedoch auch den Wert für die anderen Spalten in der Tabelle finden. Hier kommt die "RID-Suche" ins Spiel. In der Tabelle wird nach der Zeilen-ID (der RID in der RID-Suche) Table1IDgesucht, die der Zeile mit dem Wert 500000 zugeordnet ist, und die Werte aus der Table1DataSpalte abgerufen . Wenn Sie mit der Maus über den Knoten "RID-Suche" im Plan fahren, wird Folgendes angezeigt:

Geben Sie hier die Bildbeschreibung ein

Die "Ausgabeliste" enthält die von der RID-Suche zurückgegebenen Spalten.

Ein interessantes Beispiel ist eine Tabelle mit einem Clustered-Index und einem Nicht-Clustered-Index. Die folgende Tabelle enthält drei Spalten. ID ist der Clustering-Schlüssel, Datder durch einen nicht gruppierten Index indiziert wird IX_Table, und eine dritte Spalte 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

Nehmen Sie diese Beispielabfrage:

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

Wir bitten SQL Server, jede Spalte aus der Tabelle zurückzugeben, in der die DatSpalte das Wort enthält Test. Wir haben hier ein paar Möglichkeiten; Wir können uns die Tabelle ansehen (dh den Clustered-Index) - aber das würde das Scannen des gesamten Objekts bedeuten, da die Tabelle nach IDSpalten sortiert ist , was uns nichts darüber sagt, welche Zeilen Testin der DatSpalte enthalten sind. Die andere Option (und die von SQL Server ausgewählte) besteht darin, im IX_Table1nicht gruppierten Index nach der Zeile zu suchen, in der SQL Server Dat = 'Test'jedoch, da wir auch die OthSpalte benötigen , eine Suche im gruppierten Index mit einem "Schlüssel" durchführen muss Suchvorgang. Dies ist der Plan dafür:

Geben Sie hier die Bildbeschreibung ein

Wenn wir das nicht-gruppierten Index ändern , so dass es enthält die OthSpalte:

DROP INDEX IX_Table1
ON dbo.Table1;
GO

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

Führen Sie dann die Abfrage erneut aus:

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

Wir sehen jetzt eine einzelne nicht gruppierte Indexsuche, da SQL Server lediglich die Zeile Dat = 'Test'im Index suchen muss IX_Table1, die den Wert für Othund den Wert für die IDSpalte (den Primärschlüssel) enthält, die automatisch in jedem Nicht- Index vorhanden ist. Clustered-Index. Der Plan:

Geben Sie hier die Bildbeschreibung ein


5

Die Schlüsselsuche wird verursacht, weil die Engine einen Index verwendet hat, der nicht alle Spalten enthält, die Sie abrufen möchten. Der Index deckt also nicht die Spalten in der select- und where-Anweisung ab.

Um die Schlüsselsuche zu eliminieren, müssen Sie die fehlenden Spalten einschließen (die Spalten in der Ausgabeliste der Schlüsselsuche) = ProducerContactGuid, QuoteStatusID, PolicyTypeID und ProducerLocationID. Eine andere Möglichkeit besteht darin, die Abfrage zu zwingen, stattdessen den Clustered-Index zu verwenden.

Beachten Sie, dass 27 nicht gruppierte Indizes für eine Tabelle möglicherweise die Leistung beeinträchtigen. Beim Ausführen eines Updates, Einfügens oder Löschens muss SQL Server alle Indizes aktualisieren. Diese zusätzliche Arbeit kann sich negativ auf die Leistung auswirken.


Beachten Sie auch, dass zu viele Indizes die Kompilierung des Ausführungsplans verwirren und auch zu suboptimalen Auswahlen führen können.
Einseitig

4

Sie haben vergessen, das an dieser Abfrage beteiligte Datenvolumen anzugeben. Auch warum fügen Sie in eine temporäre Tabelle ein? Wenn Sie nur anzeigen müssen, führen Sie keine Einfügeanweisung aus.

Für die Zwecke dieser Abfrage gilt: tblQuotes keine 27 nicht gruppierten Indizes benötigt. Es benötigt 1 Clustered-Index und 5 Nicht-Clustered-Indizes oder vielleicht 6 Nicht-Clustered-Indexex.

Diese Abfrage möchte Indizes für diese Spalten:

QuoteStatusID
ProducerLocationID
ProducerContactGuid
EffectiveDate
LineGUID
OriginalQuoteGUID

Ich habe auch den folgenden Code bemerkt:

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

ist NON Sargable also Indizes nicht nutzen können.

Um diesen Code zu SARgableändern , ändern Sie ihn wie folgt:

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

Um Ihre Hauptfrage zu beantworten: "Warum erhalten Sie einen Schlüssel? Nachschlagen":

Sie erhalten, KEY Look upweil einige der in der Abfrage erwähnten Spalten nicht in einem Deckungsindex vorhanden sind.

Sie können googeln und über Covering Indexoder studieren Include index.

In meinem Beispiel wird angenommen, dass tblQuotes.QuoteStatusID kein Clustered-Index ist, dann kann ich auch DisplayStatus abdecken. Da möchten Sie DisplayStatus in Resultset. Jede Spalte, die nicht in einem Index und in der Ergebnismenge vorhanden ist, kann zur Vermeidung abgedeckt werden KEY Look Up or Bookmark lookup. Dies ist ein Beispiel für den Index:

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

** Haftungsausschluss: ** Denken Sie daran, dass oben nur mein Beispiel angezeigt wird. DisplayStatus kann nach der Analyse mit anderen Nicht-CIs abgedeckt werden.

Ebenso müssen Sie einen Index und einen Abdeckungsindex für die anderen an der Abfrage beteiligten Tabellen erstellen.

Sie bekommen Index SCAN auch in Ihren Plan.

Dies kann passieren, weil kein Index in der Tabelle vorhanden ist oder wenn der Optimierer bei einem großen Datenvolumen lieber scannen als eine Indexsuche durchführen möchte.

Dies kann auch aufgrund von auftreten High cardinality. Erhalten Sie mehr Zeilen als erforderlich, da die Verknüpfung fehlerhaft ist. Dies kann auch korrigiert werden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.