Pourquoi SQL Server ignorerait-il un index?

16

J'ai une table, CustPassMasteravec 16 colonnes, dont une CustNum varchar(8), et j'ai créé un index IX_dbo_CustPassMaster_CustNum. Lorsque je lance ma SELECTdéclaration:

SELECT * FROM dbo.CustPassMaster WHERE CustNum = '12345678'

Il ignore complètement l'index. Cela m'embrouille car j'ai un autre tableau CustDataMasteravec beaucoup plus de colonnes (55), dont l'une est CustNum varchar(8). J'ai créé un index sur cette colonne ( IX_dbo_CustDataMaster_CustNum) dans cette table, et utilise pratiquement la même requête:

SELECT * FROM dbo.CustDataMaster WHERE CustNum = '12345678'

Et il utilise l'index que j'ai créé.

Y a-t-il un raisonnement spécifique derrière cela? Pourquoi utiliserait-il l'index de CustDataMaster, mais pas celui de CustPassMaster? Est-ce dû au faible nombre de colonnes?

La première requête renvoie 66 lignes. Pour le second, 1 ligne est retournée.

Remarque supplémentaire: CustPassMastercontient 4991 enregistrements et CustDataMaster5376 enregistrements. Serait-ce le raisonnement derrière l'ignorance de l'index? CustPassMastercontient également des enregistrements en double qui ont également les mêmes CustNumvaleurs. Est-ce un autre facteur?

Je fonde cette revendication sur les résultats réels du plan d'exécution des deux requêtes.

Voici le DDL pour CustPassMaster(celui avec l'index inutilisé):

CREATE TABLE dbo.CustPassMaster(
    [CustNum] [varchar](8) NOT NULL,
    [Username] [char](15) NOT NULL,
    [Password] [char](15) NOT NULL,
    /* more columns here */
    [VBTerminator] [varchar](1) NOT NULL
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_dbo_CustPassMaster_CustNum] ON dbo.CustPassMaster
(
    [CustNum] ASC
) WITH (PAD_INDEX = OFF
    , STATISTICS_NORECOMPUTE = OFF
    , SORT_IN_TEMPDB = OFF
    , DROP_EXISTING = OFF
    , ONLINE = OFF
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

Et le DDL pour CustDataMaster(j'ai omis beaucoup de champs non pertinents):

CREATE TABLE dbo.CustDataMaster(
    [CustNum] [varchar](8) NOT NULL,
    /* more columns here */
    [VBTerminator] [varchar](1) NOT NULL
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_dbo_CustDataMaster_CustNum] ON dbo.CustDataMaster
(
    [CustNum] ASC
)WITH (PAD_INDEX = OFF
    , STATISTICS_NORECOMPUTE = OFF
    , SORT_IN_TEMPDB = OFF
    , DROP_EXISTING = OFF
    , ONLINE = OFF
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

Je n'ai pas d'index cluster sur aucune de ces tables, un seul index non cluster.

Ignorez le fait que les types de données ne correspondent pas entièrement au type de données stockées. Ces champs sont une sauvegarde d'une base de données IBM AS / 400 DB2, et ce sont les types de données compatibles pour celle-ci. (Je dois pouvoir interroger cette base de données de sauvegarde avec exactement les mêmes requêtes et obtenir exactement les mêmes résultats.)

Ces données ne sont utilisées que pour les SELECTdéclarations. Je ne fais aucune instruction INSERT/ UPDATE/ DELETEdessus, sauf lorsque l'application de sauvegarde copie des données de l'AS / 400.

Der Kommissar
la source
Cela pourrait valoir la peine de lire cet article sur le point de basculement de NonClustered à Clustered. sqlskills.com/blogs/kimberly/the-tipping-point-query-answers
Mark Sinkinson
3
Voilà donc la différence. Si la première requête utilise votre index, elle devra effectuer 65 recherches. C'est cher. La deuxième requête ne doit en effectuer qu'une.
Aaron Bertrand

Réponses:

18

En règle générale, les index seront utilisés par SQL Server s'il juge plus judicieux d'utiliser l'index que d'utiliser directement la table sous-jacente.

Il semblerait probable que l'optimiseur basé sur les coûts pense qu'il serait plus coûteux d'utiliser réellement l'indice en question. Vous pouvez le voir utiliser l'index si au lieu de le faire SELECT *, vous simplement SELECT T1Col1.

Lorsque vous SELECT *dites à SQL Server de renvoyer toutes les colonnes de la table. Pour renvoyer ces colonnes, SQL Server doit lire les pages des lignes qui correspondent aux WHEREcritères d'instruction de la table elle-même (index cluster ou segment de mémoire). SQL Server pense probablement que le nombre de lectures nécessaires pour obtenir le reste des colonnes de la table signifie qu'il pourrait aussi bien analyser directement la table. Il serait utile de voir la requête réelle et le plan d'exécution réel utilisé par la requête.

Max Vernon
la source
3
Une solution plus évidente et optimale serait donc pour moi de limiter les colonnes que je sélectionne, et de les inclure dans la INCLUDEclause de l'index?
Der Kommissar
1
Cela pourrait très bien faire une grande différence. L'ajout de toutes les colonnes renvoyées par la requête à la INCLUDEclause obligera probablement SQL Server à utiliser l'index. Cela dit, qu'essayez-vous d'optimiser? Il me semble que si votre table a une taille de ligne moyenne de 100 octets, alors 5000 lignes ne représentent qu'environ 500 Ko de données et ne valent peut-être pas la peine d'être dépensées.
Max Vernon
1
La taille moyenne des lignes est de 0,30 Ko pour Table1et de 0,53 Ko pour Table2. Toutes ces données sont importées d'un AS / 400 (IBM System i) et il n'y a AUCUN PK sur quoi que ce soit. J'ai créé manuellement tous les index aujourd'hui après que les gens aient mentionné que l'application est parfois assez lente.
Der Kommissar
10

Pour utiliser l'index, comme vous le faites select *, SQL Server doit d'abord lire chacune des lignes de l'index qui correspondent à la valeur que vous avez dans la clause where. Sur cette base, il obtiendra les valeurs d'index cluster pour chacune des lignes, puis il devra rechercher chacune d'elles séparément de l'index cluster (= recherche de clé). Puisque vous avez dit que les valeurs ne sont pas uniques, SQL Server utilise des statistiques pour estimer le nombre de fois où il doit effectuer cette recherche de clé.

Il est très probable que l'estimation des coûts pour l'analyse de l'index non clusterisé + des recherches de clés dépasse l'estimation des coûts pour l'analyse des index clusterisés, et c'est pourquoi l'index est ignoré.

Vous pouvez essayer d'utiliser set statistics io onpuis utiliser une indication d'index pour voir si le coût d'E / S est réellement inférieur lors de l'utilisation de l'index ou non. Si la différence est importante, vous pouvez consulter les statistiques, si elles sont obsolètes.

De plus, si votre SQL utilise réellement des variables et non les valeurs exactes, cela peut également être dû au reniflage des paramètres (= la valeur précédente utilisée pour créer le plan avait beaucoup de lignes dans le tableau).

James Z
la source
1

C'est peut-être la raison. Les optimiseurs sont basés sur le coût et décident du chemin à choisir en fonction du «coût» de chaque chemin d'exécution. Le «plus gros» coût est d'obtenir les données du disque vers la mémoire. Si l'optimiseur calcule qu'il faut plus de temps pour lire à la fois l'index et les données, il peut décider de sauter l'index. Plus les lignes sont grandes, plus elles prennent de blocs de disque.

Marco
la source