J'ai récemment commencé à travailler sur l'indexation des différentes vues de notre base de données. Souvent, certaines de ces tables ne sont utilisées que pour les jointures et sont rarement utilisées dans les instructions where ou order by. Cela signifie que nous créons plusieurs index (ou un grand index relativement moins efficace) qui sont tous saisis sur la clé primaire. Je dois préciser qu'il n'y a ni où ni ordre en contenant quoi que ce soit de ce tableau et il n'y a pas de recherche de clés dans les vues avec lesquelles j'ai des problèmes.
Si SQL a plusieurs index qui sont tous saisis sur tbl.ID, comment fait-il le choix d'utiliser un index sur l'autre? Je trouve que cela peut varier entre les vues même si les données extraites de l'index seront les mêmes. Généralement, il n'y a pas de gain/perte d'efficacité énorme dans le choix d'un indice par rapport à l'autre; ce qui m'amène à croire qu'il choisit un indice "assez bon" et passe à autre chose (car il y en a beaucoup clavardés dans la même colonne).
De plus, la création de plusieurs index avec la même clé doit-elle être évitée pour cette raison?
Le problème spécifique auquel je suis actuellement confronté est la vue qui choisit un index comme celui-ci:
CREATE NONCLUSTERED INDEX [index1]
ON a.tbl ([ID])
INCLUDE (number,name,year, ...)
Où il y a 14 colonnes dans l'inclusion (c'est regrettable, mais requis en fonction de notre structure de requête actuelle)
Sur l'indice le plus efficace:
CREATE NONCLUSTERED INDEX [index2]
ON a.tbl ([ID])
INCLUDE (number,name,year, ...)
Où il n'y a que 5 colonnes dans l'inclusion.
Les deux index contiennent les informations dont la vue a besoin, mais l'un est évidemment beaucoup plus petit et plus efficace à utiliser. L'index plus grand doit exister pour couvrir une vue massive qui appelle toutes les colonnes qu'il contient (environ la moitié du tableau).
Il vaut probablement la peine de mentionner que je pourrais choisir de créer un index massif comme ci-dessus, qui couvre presque toutes les vues légèrement moins efficacement que ces index plus petits. D'après mes tests, il semble que l'ajout de ces index supplémentaires n'ajoute pas de coût significatif à la mise à jour des tables; et j'ai donc choisi de les créer pour les petits gains de performances. En général, ils sont utilisés selon leurs besoins, mais dans certains cas, l'optimiseur choisit une option moins efficace sans raison apparente.
Dans mon expérimentation limitée:
Ce genre de sens. Si vous allez ajouter des règles pour rechercher l'index parfait dans l'un ou l'autre de ces cas, vous devez le faire quand cela vous permettra d'économiser le plus. Lors d'une recherche, l'effort dépensé pour trouver un meilleur indice quand il n'aura pas vraiment d'importance en termes d'économies pour toutes les lignes par opposition à par ligne est beaucoup moins susceptible d'être ça vaut le coup. "Last in" est peut-être intentionnel, avec l'hypothèse que le dernier index de couverture que vous avez créé est probablement le meilleur que vous avez créé, ou il peut simplement être arbitraire et coïncident (comme l'ordre des colonnes dans une recommandation d'index manquante).
Ma configuration de test était assez simple:
CREATE TABLE dbo.t1
(
id int IDENTITY(1,1) PRIMARY KEY,
sn1 sysname, tn1 sysname, cn1 sysname, typ1 sysname,
sn2 sysname, tn2 sysname, cn2 sysname, typ2 sysname,
sn3 sysname, tn3 sysname, cn3 sysname, typ3 sysname,
sn4 sysname, tn4 sysname, cn4 sysname, typ4 sysname
);
GO
SET NOCOUNT ON;
GO
INSERT dbo.t1
(
sn1,tn1,cn1,typ1,sn2,tn2,cn2,typ2,
sn3,tn3,cn3,typ3,sn4,tn4,cn4,typ4
)
SELECT s.name, t.name, c.name, typ.name,
s.name, t.name, c.name, typ.name,
s.name, t.name, c.name, typ.name,
s.name, t.name, c.name, typ.name
FROM sys.schemas AS s
CROSS JOIN sys.objects AS t
INNER JOIN sys.all_columns AS c
ON t.[object_id] = c.[object_id]
INNER JOIN sys.types AS typ
ON c.user_type_id = typ.user_type_id;
GO
CREATE VIEW dbo.v1
AS
SELECT id,sn1,tn1
FROM dbo.t1;
GO
Cela a mis 13 260 lignes dans mon tableau (vos résultats varieront). Ensuite, j'ai créé à plusieurs reprises les trois mêmes index, dans un ordre différent:
-- widest first
CREATE INDEX ix_wide ON dbo.t1(sn1)
INCLUDE(tn1,cn1,typ1,sn2,tn2,cn2,typ2,sn3,tn3,cn3,typ3,sn4,tn4,cn4,typ4);
GO
CREATE INDEX ix_mid ON dbo.t1(sn1) INCLUDE(tn1,cn1,sn2,tn2,cn2,typ2,sn3,tn3);
GO
CREATE INDEX ix_small ON dbo.t1(sn1) INCLUDE(tn1,cn1);
GO
-- widest last = ix_small then ix_mid then ix_wide
-- middle1 = ix_mid then ix_wide then ix_small
-- middle2 = ix_small then ix_wide then ix_mid
Ensuite, dans chacun de ces quatre cas, j'ai exécuté ces deux requêtes et étudié les plans:
DBCC FREEPROCCACHE;
GO
SELECT id,sn1,tn1 FROM dbo.v1; -- scan
GO
SELECT id,sn1,tn1 FROM dbo.v1 WHERE sn1 LIKE N'q%'; -- seek
Résultats:
widest first widest last middle1 middle2
------- ------------ ----------- -------- --------
scan ix_small ix_small ix_small ix_small
seek ix_small ix_wide ix_small ix_mid
L'analyse a toujours choisi l'index le plus étroit. La recherche a toujours choisi l'index créé en dernier (puisque toutes les couvertures). Je n'ai pas étendu les tests pour inclure également un indice non couvrant dans le mélange, car je ne pense pas que cela changera ce résultat.
Il y a deux morales ici:
Probablement quelques internes et heuristiques qui me manquent ici que Paul clarifiera. J'ai également essayé d'activer l'indicateur de trace 302 pour afficher les informations de sélection d'index, mais cela ne semble pas fonctionner sur les versions modernes de SQL Server.