Pourquoi ma clause WHERE bénéficie-t-elle d'une colonne «incluse»?

12

Selon cette réponse , à moins qu'un index ne soit construit sur les colonnes qui sont utilisées pour restreindre, la requête ne bénéficiera pas d'un index.

J'ai cette définition:

CREATE TABLE [dbo].[JobItems] (
    [ItemId]             UNIQUEIDENTIFIER NOT NULL,
    [ItemState]          INT              NOT NULL,
    [ItemPriority]       INT NOT NULL,
    [CreationTime]       DATETIME         NULL DEFAULT GETUTCDATE(),
    [LastAccessTime]     DATETIME         NULL DEFAULT GETUTCDATE(),
     -- other columns
 );

 CREATE UNIQUE CLUSTERED INDEX [JobItemsIndex]
    ON [dbo].[JobItems]([ItemId] ASC);
 GO

CREATE INDEX [GetItemToProcessIndex]
    ON [dbo].[JobItems]([ItemState], [ItemPriority], [CreationTime])
    INCLUDE (LastAccessTime);
GO

et cette requête:

UPDATE TOP (150) JobItems 
SET ItemState = 17 
WHERE 
    ItemState IN (3, 9, 10)
    AND LastAccessTime < DATEADD (day, -2, GETUTCDATE()) 
    AND CreationTime < DATEADD (day, -2, GETUTCDATE());

J'ai passé en revue le plan réel, et il n'y a qu'une seule recherche d'index avec le prédicat exactement comme dans le WHERE- aucune "recherche de signet" supplémentaire à récupérer LastAccessTimemême si ce dernier est seulement "inclus" dans l'index, ne fait pas partie de l'index.

Il me semble que ce comportement contredit la règle selon laquelle la colonne doit faire partie de l'index, et pas seulement "incluse".

Le comportement que j'observe est-il le bon? Comment puis-je savoir à l'avance si mes WHEREavantages d'une colonne incluse ou si la colonne doit faire partie de l'indice?

acéré
la source
Il peut toujours rechercher en fonction de la ItemStatevaleur, mais la recherche ne sera pas aussi efficace que si votre indice était structuré comme suit(ItemState, CreationTime, LastAccessTime)
Mark Sinkinson
1
@MarkSinkinson or just(ItemState, CreationTime) INCLUDE (LastAccessTime)
ypercubeᵀᴹ
@sharptooth la réponse liée que vous avez ne dit pas cela ("à moins qu'un index ne soit construit sur les colonnes qui sont utilisées pour restreindre la requête ne bénéficiera pas d'un index"). Il indique qu'un index sur (a,b)n'est pas le meilleur pour une requête avec SELECT a FROM t WHERE b=5;et qu'un index sur (b) INCLUDE (a)est bien meilleur.
ypercubeᵀᴹ

Réponses:

9

Votre prédicat est différent de votre prédicat de recherche.

Un prédicat de recherche est utilisé pour rechercher les données ordonnées dans l'index. Dans ce cas, il effectuera trois recherches, une pour chaque ItemState qui vous intéresse. Au-delà, les données sont dans l'ordre ItemPriority, donc aucune autre opération "Seek" ne peut être effectuée.

Mais avant de renvoyer les données, il vérifie chaque ligne à l'aide du prédicat, que j'appelle le prédicat résiduel. Cela se fait sur les résultats du Seek Predicate.

Toute colonne incluse ne fait pas partie des données ordonnées, mais peut être utilisée pour satisfaire le prédicat résiduel, sans avoir à effectuer la recherche supplémentaire.

Vous pouvez voir le matériel que j'ai écrit à ce sujet autour de Sargability. Recherchez une session sur SQLBits en particulier, sur http://bit.ly/Sargability

Modifier: pour mieux montrer l'impact des résidus, exécutez la requête à l'aide de la fonction non documentée OPTION (QUERYTRACEON 9130), qui séparera le résidu en un opérateur de filtre distinct (qui est en fait une version antérieure du plan avant que le résidu ne soit déplacé dans l'opérateur de recherche). Il montre clairement l'impact d'une recherche inefficace, par le nombre de lignes transmises au filtre.

Il convient également de noter qu'en raison de la clause IN sur ItemState, les données transmises à gauche sont en fait dans l'ordre ItemState, et non dans l'ordre ItemPriority. Un index composite sur ItemState suivi d'une des dates (par exemple (ItemState, LastAccessTime)) pourrait être utilisé pour avoir trois recherches (notez que le prédicat de recherche affiche trois recherches au sein de l'opérateur de recherche), chacun contre deux niveaux, produisant des données qui sont toujours dans l'ordre ItemState (par exemple, ItemState = 3 et LastAccessTime moins que quelque chose, puis ItemState = 9 et LastAccessTime moins que quelque chose, puis ItemState = 10 et LastAccessTime moins que quelque chose).

Un index sur (ItemState, LastAccesTime, CreationTime) ne serait pas plus utile qu'un sur (ItemState, LastAccessTime) car le niveau CreationTime n'est utile que si votre recherche concerne une combinaison ItemState et LastAccessTime particulière, pas une plage. Comme si l'annuaire téléphonique n'est pas dans l'ordre du prénom si vous êtes intéressé par les noms commençant par F.

Si vous voulez un index composite mais que vous ne pourrez jamais utiliser les colonnes ultérieures dans Seek Predicates en raison de la façon dont vous utilisez les colonnes précédentes, vous pouvez aussi les avoir comme colonnes incluses, où elles prennent moins de place dans le index (car ils ne sont stockés qu'au niveau feuille de l'index, pas aux niveaux supérieurs) mais peuvent toujours éviter les recherches et être utilisés dans les prédicats résiduels.

Selon le terme prédicat résiduel - c'est mon propre terme pour cette propriété d'une recherche. Une jointure de fusion l'appelle explicitement son équivalent un prédicat résiduel, et la correspondance de hachage appelle celle-ci un résidu de sonde (que vous pourriez obtenir de TSA si vous correspondez pour le hachage). Mais dans une recherche, ils l'appellent simplement Predicate, ce qui le rend moins mauvais qu'il ne l'est.

Rob Farley
la source
3

GetItemToProcessIndex n'est pas entièrement consultable car votre clause where est activée ItemState + LastAccessTime + CreationTime. Les colonnes indexées et la clause where ne correspondent pas parfaitement.

Si vous créez un index de couverture sur ItemState + LastAccessTime + CreationTime, pour chaque correspondance obtenue à partir de GetItemToProcessIndex, vous obtenez également la valeur de votre clé primaire (ItemId). Il suffit de s'assurer que la 2e date correspond.

C'est tout ce dont vous avez besoin pour accéder à l'emplacement de la ligne sur sa page et la mettre à jour.

Avec votre index actuel, il peut aider le serveur à trouver des lignes avec le ItemState que vous voulez, mais il doit ensuite les lire toutes à partir de l'index afin de trouver les correspondances correctes sur LastAccessTime + CreationTime. En fonction des prédicats de date et de la taille de l'ensemble correspondant et de ce qui doit être exclu, cela peut entraîner beaucoup plus d'E / S qu'un index parfaitement couvrant sur les 3 colonnes uniquement qui rechercherait ItemState et la deuxième colonne (1ère date indexée) . La deuxième date dans l'index peut être incluse cependant. Les colonnes supplémentaires ne doivent pas être indexées entre ces 3, bien que cela puisse être correct comme 4ème colonne (voir la réponse de rob à propos des colonnes supplémentaires).

Julien Vavasseur
la source