web-dev-qa-db-fra.com

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

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

La table tblQuotes a déjà un index clusterisé (sur QuoteID) et 27 index non clusterisés, donc j'essaye de ne plus en créer.

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

Plan d'exécution ici .

Ou regardez-le:

enter image description here

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

enter image description here

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:

enter image description here

16
Serdia

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:

enter image description here

La requête examine le IX_Table1 index pour trouver la ligne avec Table1ID = 5000000 car 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 pour les 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 le Table1ID valeur de 500000, obtention des valeurs à partir de Table1Data colonne. Si vous passez la souris sur le nœud "RID Lookup" dans le plan, vous voyez ceci:

enter image description here

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 clustering, Dat qui est indexé par un index non clusterisé 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 colonne Dat contient le mot Test. Nous avons ici deux choix; nous pouvons regarder la table (c.-à-d. l'index cluster) - mais cela impliquerait de balayer la chose entière puisque la table est ordonnée par la colonne ID, qui ne nous dit rien sur la ou les lignes qui contiennent Test dans la colonne Dat. L'autre option (et celle choisie par SQL Server) consiste à rechercher dans le IX_Table1 index non clusterisé pour trouver la ligne où Dat = 'Test', cependant, étant donné que nous avons également besoin de la colonne Oth, SQL Server doit effectuer une recherche dans l'index cluster à l'aide d'une opération "Recherche de clé". Voici le plan pour cela:

enter image description here

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

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 recherche d'index non cluster unique, car SQL Server doit simplement localiser la ligne où Dat = 'Test' dans le IX_Table1 index, qui inclut la valeur de Oth et la valeur de la colonne ID (la clé primaire), qui est automatiquement présente dans chaque index non clusterisé. Le plan:

enter image description here

23
Max Vernon

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. L'index ne couvre donc 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 façon 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.

6
Daniel Björk

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, tblQuotes n'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

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

Pour que ce code SARgable le change 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 up car certaines des colonnes mentionnées dans la requête ne sont pas présentes dans un index de couverture.

Vous pouvez google et étudier sur Covering Index ou 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é.

4
KumarHarsh