Élimine l'opérateur de recherche de clés (en cluster) qui ralentit les performances

16

Comment puis-je éliminer un opérateur de recherche de clé (en cluster) dans mon plan d'exécution?

Le tableau tblQuotesa déjà un index ordonné en clusters (sur QuoteID) et 27 index non - cluster, donc je suis en train de ne pas créer plus.

J'ai mis la colonne d'index cluster QuoteIDdans ma requête, en espérant que cela aiderait - mais malheureusement toujours la même.

Plan d'exécution ici .

Ou regardez-le:

entrez la description de l'image ici

Voici ce que dit l'opérateur de recherche de clé:

entrez la description de l'image ici

Requete:

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

Plan d'exécution:

entrez la description de l'image ici

Serdia
la source
Les lignes Estimé vs Réel montrent une différence notable. Peut-être que SQL a choisi un mauvais plan car il n'a pas les données pour faire une bonne estimation. À quelle fréquence mettez-vous à jour vos statistiques?
RDFozz

Réponses:

23

Des recherches de clés de différentes saveurs se produisent lorsque le processeur de requêtes doit obtenir des valeurs à partir de colonnes qui ne sont pas stockées dans l'index utilisé pour localiser les lignes requises pour que la requête renvoie des résultats.

Prenons par exemple le code suivant, où nous créons une table avec un seul index:

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

Nous allons insérer 1 000 000 lignes dans le tableau afin que nous ayons quelques données à utiliser:

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

Maintenant, nous allons interroger les données avec l'option pour afficher le plan d'exécution "réel":

SELECT *
FROM dbo.Table1
WHERE Table1ID = 500000;

Le plan de requête montre:

entrez la description de l'image ici

La requête examine l' IX_Table1index pour trouver la ligne, Table1ID = 5000000car la consultation de cet index est beaucoup plus rapide que l'analyse de la table entière à la recherche de cette valeur. Cependant, pour satisfaire les résultats de la requête, le processeur de requêtes doit également trouver la valeur des autres colonnes de la table; c'est là que la "RID Lookup" entre en jeu. Il recherche dans le tableau l'ID de ligne (le RID dans RID Lookup) associé à la ligne contenant la Table1IDvaleur de 500000, obtenant les valeurs de la Table1Datacolonne. Si vous passez la souris sur le nœud "RID Lookup" dans le plan, vous voyez ceci:

entrez la description de l'image ici

La "Liste de sortie" contient les colonnes renvoyées par la recherche RID.

Une table avec un index cluster et un index non cluster constitue un exemple intéressant. Le tableau ci-dessous comporte trois colonnes; ID qui est la clé de cluster, Datqui est indexé par un index non-cluster IX_Table, et une troisième colonne, 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

Prenez cet exemple de requête:

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

Nous demandons à SQL Server de renvoyer chaque colonne de la table où la Datcolonne contient le mot Test. Nous avons quelques choix ici; nous pouvons regarder la table (c.-à-d. l'index clusterisé) - mais cela impliquerait de scanner la chose entière puisque la table est ordonnée par la IDcolonne, qui ne nous dit rien sur la ou les lignes qui contiennent Testdans la Datcolonne. L'autre option (et celle choisie par SQL Server) consiste à rechercher dans l' IX_Table1index non cluster pour trouver la ligne où Dat = 'Test', cependant, puisque nous avons également besoin de la Othcolonne, SQL Server doit effectuer une recherche dans l'index cluster à l'aide d'une clé " Rechercher ". Voici le plan pour cela:

entrez la description de l'image ici

Si nous modifions l'index non clusterisé pour qu'il inclue la Othcolonne:

DROP INDEX IX_Table1
ON dbo.Table1;
GO

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

Réexécutez ensuite la requête:

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

Nous voyons maintenant une seule recherche d'index non cluster, car SQL Server a simplement besoin de localiser la ligne où Dat = 'Test'dans l' IX_Table1index, qui inclut la valeur de Oth, et la valeur de la IDcolonne (la clé primaire), qui est automatiquement présente dans chaque non index clusterisé. Le plan:

entrez la description de l'image ici

Max Vernon
la source
6

La recherche de clé est due au fait que le moteur a choisi d'utiliser un index qui ne contient pas toutes les colonnes que vous essayez de récupérer. Ainsi, l'index ne couvre pas les colonnes de l'instruction select and where.

Pour éliminer la recherche de clé, vous devez inclure les colonnes manquantes (les colonnes de la liste de sortie de la recherche de clé) = ProducerContactGuid, QuoteStatusID, PolicyTypeID et ProducerLocationID ou une autre méthode consiste à forcer la requête à utiliser l'index clusterisé à la place.

Notez que 27 index non cluster sur une table peuvent être mauvais pour les performances. Lors de l'exécution d'une mise à jour, d'une insertion ou d'une suppression, SQL Server doit mettre à jour tous les index. Ce travail supplémentaire peut affecter négativement les performances.

Daniel Björk
la source
Notez également qu'un trop grand nombre d'index peut perturber la compilation du plan d'exécution et peut également entraîner des sélections sous-optimales.
Lopsided
4

Vous avez oublié de mentionner le volume de données impliqué dans cette requête. Aussi pourquoi insérez-vous dans une table temporaire? Si seulement vous devez afficher, n'exécutez pas d'instruction d'insertion.

Pour les besoins de cette requête, tblQuotesn'a pas besoin de 27 index non clusterisés. Il a besoin d'un index cluster et de 5 index non cluster ou, peut-être de 6 indexex non cluster.

Cette requête voudrait des index sur ces colonnes:

QuoteStatusID
ProducerLocationID
ProducerContactGuid
EffectiveDate
LineGUID
OriginalQuoteGUID

J'ai également remarqué le code suivant:

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

c'est NON Sargable-à - dire qu'il ne peut pas utiliser d'index.

Pour que ce code le SARgablechange en ceci:

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

Pour répondre à votre question principale, "pourquoi vous obtenez une clé Rechercher":

Vous obtenez KEY Look upparce que certaines des colonnes mentionnées dans la requête ne sont pas présentes dans un index de couverture.

Vous pouvez google et étudier Covering Indexou Include index.

Dans mon exemple, supposons que tblQuotes.QuoteStatusID est un index non clusterisé, alors je peux également couvrir DisplayStatus. Puisque vous voulez DisplayStatus dans Resultset. Toute colonne qui n'est pas présente dans un index et qui est présente dans l'ensemble de résultats peut être couverte pour éviter KEY Look Up or Bookmark lookup. Voici un exemple d'index couvrant:

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

** Avis de non-responsabilité: ** N'oubliez pas que ci-dessus est seulement mon exemple DisplayStatus peut être couvert avec d'autres non CI après analyse.

De même, vous devrez créer un index et un index de couverture sur les autres tables impliquées dans la requête.

Vous obtenez Index SCANégalement dans votre plan.

Cela peut se produire car il n'y a pas d'index sur la table ou lorsqu'il y a un grand volume de données, l'optimiseur peut décider d'analyser plutôt que d'effectuer une recherche d'index.

Cela peut également se produire en raison de High cardinality. Obtention d'un plus grand nombre de lignes que nécessaire en raison d'une jointure défectueuse. Cela peut également être corrigé.

KumarHarsh
la source