web-dev-qa-db-fra.com

Comment SQL Server choisit-il les index à utiliser

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.

7
Thomas D.

Dans mon expérimentation limitée:

  • Lorsque la requête doit effectuer une analyse, SQL Server fait le travail pour trouver l'index le plus étroit à chaque fois.
  • Lorsque la requête doit effectuer une recherche, elle ne se soucie pas de la largeur de l'index, elle utilise simplement le dernier index de couverture créé.

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).

Preuve (enfin, sorte de preuve)

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.

Moralité (enfin, sorte de morale)

Il y a deux morales ici:

  1. Créez d'abord vos index les plus larges si vous souhaitez que vos index les plus étroits soient utilisés dans les recherches lorsqu'ils le peuvent.
  2. C'est probablement un ensemble restreint de cas d'utilisation où cette optimisation en vaut la peine. Tels que les recherches qui retournent beaucoup de lignes et l'index de couverture plus large a de très grandes colonnes qui ne sont pas dans l'index plus étroit. Dans ces cas, il peut être utile de spécifier explicitement l'index avec un indice (et toutes les mises en garde que cela implique) plutôt que de s'appuyer sur le comportement que j'ai observé 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.

9
Aaron Bertrand