J'ai une table, CustPassMaster
avec 16 colonnes, dont CustNum varchar(8)
, et j'ai créé un index IX_dbo_CustPassMaster_CustNum
. Lorsque j'exécute mon instruction SELECT
:
SELECT * FROM dbo.CustPassMaster WHERE CustNum = '12345678'
Il ignore complètement l'index. Cela m'embrouille car j'ai une autre table CustDataMaster
avec beaucoup plus de colonnes (55), dont CustNum varchar(8)
. J'ai créé un index sur cette colonne (IX_dbo_CustDataMaster_CustNum
) Dans cette table, et j'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: CustPassMaster
a 4991 enregistrements et CustDataMaster
a 5376 enregistrements. Serait-ce le raisonnement derrière l'ignorance de l'index? CustPassMaster
a également des enregistrements en double qui ont également les mêmes valeurs CustNum
. 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 les requêtes exactement les mêmes, et obtenir les résultats exactement les mêmes.)
Ces données sont uniquement utilisées pour les instructions SELECT
. Je ne fais aucune instruction INSERT
/UPDATE
/DELETE
dessus, sauf lorsque l'application de sauvegarde copie les données de l'AS/400.
En règle générale, les index sont 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 faire SELECT *
, vous avez simplement SELECT T1Col1
.
Quand vous SELECT *
vous 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 critères de l'instruction WHERE
à partir de la table elle-même (index cluster ou tas). SQL Server pense probablement que le nombre de lectures nécessaires pour obtenir le reste des colonnes de la table signifie qu'il pourrait tout aussi bien analyser la table directement. Il serait utile de voir la requête réelle et le plan d'exécution réel utilisé par la requête.
Pour utiliser l'index, parce que vous 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 on
, puis utilisez 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).
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.