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.
Ou regardez-le:
Voici ce que dit l'opérateur de recherche de clé:
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:
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:
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:
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:
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:
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.
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é.