web-dev-qa-db-fra.com

Pourquoi cette requête n'utilise-t-elle pas mon index non cluster et comment puis-je la créer?

En tant que suivi de cette question sur l'augmentation des performances des requêtes, je voudrais savoir s'il existe un moyen de rendre mon index utilisé par défaut.

Cette requête s'exécute en environ 2,5 secondes:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

Celui-ci fonctionne en environ 33 ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

Il y a un index cluster sur le champ [ID] (pk) et il y a un index non cluster sur [DateEntered], [DeviceID]. La première requête utilise l'index clusterisé, la deuxième requête utilise mon index non clusterisé. Ma question est en deux parties:

  • Pourquoi, puisque les deux requêtes ont une clause WHERE dans le champ [DateEntered], le serveur utilise-t-il l'index clusterisé sur le premier, mais pas sur le second?
  • Comment puis-je faire en sorte que l'index non cluster soit utilisé par défaut sur cette requête même sans le orderby? (Ou pourquoi ne voudrais-je pas ce comportement?)
12
Nate

la première requête effectue une analyse de table basée sur le seuil que j'ai expliqué précédemment dans: Est-il possible d'augmenter les performances de requête sur une table étroite avec des millions de lignes?

(très probablement votre requête sans la clause TOP 1000 renverra plus de 46k lignes. ou certaines entre 35k et 46k. (la zone grise ;-))

la deuxième requête doit être commandée. Étant donné que votre index NC est ordonné dans l'ordre que vous souhaitez, l'optimiseur utilise moins cher cet index, puis les recherches de signet vers l'index clusterisé pour obtenir les colonnes manquantes par rapport à une analyse d'index clusterisé et nécessitant ensuite pour commander cela.

inversez l'ordre des colonnes dans la clause ORDER BY et vous êtes de retour à une analyse d'index en cluster puisque le NC INDEX est alors inutile.

edit a oublié la réponse à votre deuxième question, pourquoi vous ne voulez pas cela

L'utilisation d'un index non couvrant non groupé signifie qu'un rowID est recherché dans l'index NC et que les colonnes manquantes doivent être recherchées dans l'index cluster (l'index cluster contient toutes les colonnes d'une table). Les entrées-sorties pour rechercher les colonnes manquantes dans l'index cluster sont des entrées-sorties aléatoires.

La clé ici est RANDOM. car pour chaque ligne trouvée dans l'index NC, les méthodes d'accès doivent rechercher une nouvelle page dans l'index clusterisé. C'est aléatoire et donc très cher.

Maintenant, d'un autre côté, l'optimiseur peut également opter pour une analyse d'index en cluster. Il peut utiliser les cartes d'allocation pour rechercher des plages d'analyse et commencer à lire l'index clusterisé en gros morceaux. C'est séquentiel et beaucoup moins cher. (tant que votre table n'est pas fragmentée :-)) L'inconvénient est que l'index cluster ENTIER doit être lu. C'est mauvais pour votre tampon et potentiellement une énorme quantité d'E/S. mais encore, des E/S séquentielles.

Dans votre cas, l'optimiseur décide entre 35 000 et 46 000 lignes, il est moins coûteux d'effectuer un balayage d'index complet en cluster. Ouais, c'est faux. Et dans de nombreux cas avec des index étroits non groupés avec des clauses WHERE non sélectives ou une grande table, cela ne va pas. (Votre table est pire, car c'est aussi une table très étroite.)

Maintenant, l'ajout de ORDER BY Rend plus coûteux l'analyse de l'index cluster complet, puis la commande des résultats. Au lieu de cela, l'optimiseur suppose qu'il est moins cher d'utiliser l'index NC déjà commandé, puis de payer la pénalité aléatoire IO pour les recherches de signets).

Votre commande est donc une solution de type "indice de requête" parfaite. MAIS, à un certain moment, une fois que les résultats de votre requête sont si importants, la pénalité pour les E/S aléatoires de recherche de signets sera si grande qu'elle deviendra plus lente. Je suppose que l'optimiseur changera de nouveau les plans pour l'analyse d'index en cluster avant ce point, mais vous ne savez jamais avec certitude.

Dans votre cas, tant que vos insertions sont commandées par entereddate, comme discuté dans le chat et la question précédente (voir lien), vous êtes mieux de créer l'index cluster sur la colonne entréDate.

9
Edward Dortland

L'expression de la requête à l'aide d'une syntaxe différente peut parfois aider à communiquer votre souhait d'utiliser un index non clusterisé à l'optimiseur. Vous devriez trouver que le formulaire ci-dessous vous donne le plan que vous souhaitez:

SELECT
    [ID],
    [DeviceID],
    [IsPUp],
    [IsWebUp],
    [IsPingUp],
    [DateEntered]
FROM [dbo].[Heartbeats]
WHERE
    [ID] IN
(
    -- Keys
    SELECT TOP (1000)
        [ID]
    FROM [dbo].[Heartbeats]
    WHERE 
        [DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
        AND [DateEntered]  < CONVERT(datetime, '2011-08-31', 121)
);

Query Plan

Comparez ce plan avec celui produit lorsque l'index non clusterisé est forcé avec un indice:

SELECT TOP (1000) 
    * 
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE 
    [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

Forced Index Hint Plan

Les plans sont essentiellement les mêmes (une recherche de clé n'est rien de plus qu'une recherche sur l'index clusterisé). Les deux formulaires de plan n'effectueront qu'une seule recherche sur l'index non cluster et un maximum de 1 000 recherches dans l'index cluster.

La différence importante réside dans la position de l'opérateur Top. Positionné entre les deux recherches, le sommet empêche l'optimiseur de remplacer les deux opérations de recherche par une analyse logiquement équivalente de l'index clusterisé. L'optimiseur fonctionne en remplaçant des parties d'un plan logique par des opérations relationnelles équivalentes. Top n'est pas un opérateur relationnel, donc la réécriture empêche la transformation en une analyse d'index en cluster. Si l'optimiseur était en mesure de repositionner l'opérateur Top, il préférerait toujours le scan à la recherche + recherche en raison du fonctionnement de l'estimation des coûts.

Coût des analyses et des recherches

À un niveau très élevé, le modèle de coût de l'optimiseur pour les numérisations et les recherches est assez simple: il estime que 320 recherches aléatoires coûtent le même que la lecture de 1350 pages dans une numérisation. Cela ressemble probablement peu aux capacités matérielles d'un système d'E/S moderne particulier, mais il fonctionne raisonnablement bien comme modèle pratique.

Le modèle fait également un certain nombre d'hypothèses simplificatrices, la principale étant que chaque requête est supposée commencer sans données ni pages d'index déjà dans le cache. L'implication est que chaque E/S entraînera une E/S physique - bien que ce soit rarement le cas dans la pratique. Même avec un cache froid, la prélecture et la lecture anticipée signifient que les pages nécessaires sont en fait très probablement en mémoire au moment où le processeur de requêtes en a besoin.

Une autre considération est que la première demande pour une ligne qui n'est pas en mémoire provoquera l'extraction de la page entière du disque. Les demandes ultérieures de lignes sur la même page n'entraîneront très probablement pas d'E/S physiques. Le modèle de calcul des coûts contient une logique pour prendre en compte des effets comme celui-ci, mais il n'est pas parfait.

Toutes ces choses (et plus) signifient que l'optimiseur a tendance à basculer vers une analyse plus tôt que prévu. Les E/S aléatoires ne sont "beaucoup plus chères" que les E/S "séquentielles" si une opération physique en résulte - l'accès aux pages en mémoire est en effet très rapide. Même lorsqu'une lecture physique est requise, une analyse peut ne pas donner lieu du tout à des lectures séquentielles en raison de la fragmentation et les recherches peuvent être colocalisées de telle sorte que le motif est essentiellement séquentiel. Ajoutez à cela les performances changeantes des systèmes d'E/S modernes (en particulier les semi-conducteurs) et le tout commence à sembler très fragile.

Objectifs de ligne

La présence d'un opérateur Top dans un plan modifie l'approche des coûts. L'optimiseur est suffisamment intelligent pour savoir que la recherche de 1 000 lignes à l'aide d'une analyse ne nécessitera probablement pas l'analyse de l'index cluster complet - il peut s'arrêter dès que 1 000 lignes ont été trouvées. Il définit un `` objectif de ligne '' de 1000 lignes à l'opérateur supérieur et utilise des informations statistiques pour revenir à partir de là pour estimer le nombre de lignes dont il s'attend à avoir besoin de la source de ligne (une analyse dans ce cas). J'ai écrit sur les détails de ce calcul ici .

Les images de cette réponse ont été créées à l'aide de SQL Sentry Plan Explorer .

20
Paul White 9