Ne vous méprenez pas; J'adore les index filtrés. Ils créent des opportunités pour une utilisation beaucoup plus efficace des E/S et nous permettent enfin d'implémenter des contraintes uniques conformes à ANSI (où plus d'un NULL est autorisé). Cependant, ils sont loin d'être parfaits. Je voulais souligner quelques domaines où les index filtrés pourraient être améliorés et les rendre beaucoup plus utiles et pratiques pour une grande partie des charges de travail.
Tout d'abord, la bonne nouvelle
Les index filtrés peuvent traiter très rapidement des requêtes auparavant coûteuses et le faire en utilisant moins d'espace (et donc des E/S réduites, même lorsqu'elles sont analysées).
Un exemple rapide utilisant Sales.SalesOrderDetailEnlarged
(construit à l'aide de ce script par Jonathan Kehayias (@SQLPoolBoy)). Cette table comporte 4,8 MM de lignes, avec 587 Mo de données et 363 Mo d'index. Il n'y a qu'une seule colonne nullable, CarrierTrackingNumber
, alors jouons avec celui-là. En l'état, la table a actuellement environ la moitié de ces valeurs (2,4 MM) comme NULL. Je vais réduire cela à environ 240 000 pour simuler un scénario dans lequel un petit pourcentage des lignes de la table sont réellement éligibles pour un index, afin de mieux mettre en évidence les avantages d'un index filtré. La requête suivante affecte 2,17 MM de lignes, laissant 241 507 lignes avec une valeur NULL pour CarrierTrackingNumber
:
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = 'x' WHERE CarrierTrackingNumber IS NULL AND SalesOrderID % 10 <> 3;
Maintenant, disons qu'il y a une exigence commerciale où nous voulons constamment examiner les commandes qui ont des produits auxquels un numéro de suivi n'a pas encore été attribué (pensez aux commandes qui sont divisées et expédiées séparément). Sur la table actuelle, nous exécuterions ces requêtes (et j'ai ajouté les commandes DBCC pour garantir le cache à froid dans tous les cas) :
DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; SELECT COUNT(*) FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL; SELECT ProductID, SalesOrderID FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL;
Qui nécessitent des analyses d'index en cluster et génèrent les métriques d'exécution suivantes (telles que capturées avec SQL Sentry Plan Explorer) :
Autrefois (c'est-à-dire depuis SQL Server 2005), nous aurions créé cet index (et en fait, même dans SQL Server 2012, c'est l'index recommandé par SQL Server) :
CREATE INDEX IX_NotVeryHelpful ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]);
Avec cet index en place et en exécutant à nouveau les requêtes ci-dessus, voici les métriques, les deux requêtes utilisant une recherche d'index comme vous pouvez vous y attendre :
Et puis en supprimant cet index et en en créant un légèrement différent, en ajoutant simplement un WHERE
clause :
CREATE INDEX IX_Filtered_CTNisNULL ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]) WHERE CarrierTrackingNumber IS NULL;
Nous obtenons ces résultats, et les deux requêtes utilisent l'index filtré pour leurs recherches :
Voici l'espace supplémentaire requis par chaque index, par rapport à la réduction du temps d'exécution et des E/S des requêtes ci-dessus :
Index | Espace d'index | Espace ajouté | Durée | Lectures |
---|---|---|---|---|
Pas d'index dédié | 363 Mo | 15 700 ms | ~164 000 | |
Index non filtré | 530 Mo | 167 Mo (+46 %) | 169ms | 1 084 |
Index filtré | 367 Mo | 4 Mo (+1 %) | 170 ms | 1 084 |
Ainsi, comme vous pouvez le voir, l'index filtré offre des améliorations de performances presque identiques à l'index non filtré (puisque les deux sont capables d'obtenir leurs données en utilisant le même nombre de lectures), mais à un stockage beaucoup plus faible coût, puisque l'index filtré n'a qu'à stocker et maintenir les lignes qui correspondent au prédicat de filtre.
Maintenant, remettons la table à son état d'origine :
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = NULL WHERE CarrierTrackingNumber = 'x'; DROP INDEX IX_NotVeryHelpful ON Sales.SalesOrderDetailEnlarged; DROP INDEX IX_Filtered_CTNisNULL ON Sales.SalesOrderDetailEnlarged;
Tim Chapman (@chapmandew) et Michelle Ufford (@sqlfool) ont fait un travail fantastique en décrivant les avantages des index filtrés en termes de performances, et vous devriez également consulter leurs publications :
- Michelle Ufford :Index filtrés :ce que vous devez savoir
- Tim Chapman :les joies des index filtrés
Aussi, des contraintes uniques conformes à l'ANSI (en quelque sorte)
Je pensais également mentionner brièvement les contraintes uniques conformes à la norme ANSI. Dans SQL Server 2005, nous créerions une contrainte unique comme celle-ci :
CREATE TABLE dbo.Personnel ( EmployeeID INT PRIMARY KEY, SSN CHAR(9) NULL, -- ... other columns ... CONSTRAINT UQ_SSN UNIQUE(SSN) );
(Nous pourrions également créer un index unique non clusterisé au lieu d'une contrainte ; l'implémentation sous-jacente est essentiellement la même.)
Maintenant, ce n'est pas un problème si les SSN sont connus au moment de la saisie :
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(1,'111111111'),(2,'111111112');
C'est également bien si nous avons occasionnellement un SSN qui n'est pas connu au moment de l'entrée (pensez à un demandeur de visa ou peut-être même à un travailleur étranger qui n'a pas de SSN et n'en aura jamais):
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(3,NULL);
Jusqu'ici tout va bien. Mais que se passe-t-il quand nous avons une seconde employé avec un SSN inconnu ?
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(4,NULL);
Résultat :
Msg 2627, Niveau 14, État 1, Ligne 1Violation de la contrainte UNIQUE KEY 'UQ_SSN'. Impossible d'insérer une clé en double dans l'objet 'dbo.Personnel'. La valeur de clé en double est (
L'instruction a été terminée.
Ainsi, à tout moment, une seule valeur NULL peut exister dans cette colonne. Contrairement à la plupart des scénarios, il s'agit d'un cas où SQL Server traite deux valeurs NULL comme égales (plutôt que de déterminer que l'égalité est simplement inconnue et, à son tour, fausse). Les gens se plaignent de cette incohérence depuis des années.
S'il s'agit d'une exigence, nous pouvons désormais contourner ce problème en utilisant des index filtrés :
ALTER TABLE dbo.Personnel DROP CONSTRAINT UQ_SSN; GO CREATE UNIQUE INDEX UQ_SSN ON dbo.Personnel(SSN) WHERE SSN IS NOT NULL;
Maintenant, notre 4ème insertion fonctionne très bien, puisque l'unicité n'est appliquée que sur les valeurs non NULL. C'est une sorte de triche, mais cela répond aux exigences de base prévues par la norme ANSI (même si SQL Server ne nous permet pas d'utiliser ALTER TABLE ... ADD CONSTRAINT
syntaxe pour créer une contrainte unique filtrée).
Mais, tenez le téléphone
Ce sont d'excellents exemples de ce que nous pouvons faire avec les index filtrés, mais il y a beaucoup de choses que nous ne pouvons toujours pas faire, et plusieurs limitations et problèmes qui en résultent.
Mises à jour des statistiques
C'est l'une des limitations les plus importantes à mon humble avis. Les index filtrés ne bénéficient pas de la mise à jour automatique des statistiques en fonction d'un changement en pourcentage du sous-ensemble de la table identifié par le prédicat de filtre ; il est basé (comme tous les index non filtrés) sur le taux de désabonnement par rapport à l'ensemble de la table. Cela signifie que, selon le pourcentage de la table dans l'index filtré, le nombre de lignes dans l'index peut quadrupler ou diminuer de moitié et les statistiques ne seront pas mises à jour à moins que vous ne le fassiez manuellement. Kimberly Tripp a donné d'excellentes informations à ce sujet (et Gail Shaw cite un exemple où il a fallu 257 000 mises à jour avant que les statistiques ne soient mises à jour pour un index filtré qui ne contenait que 10 000 lignes) :
http://www.sqlskills.com/blogs/kimberly/filtered-indexes-and-filtered-stats-might-become-seriously-out-of-date/
http://www.sqlskills.com/ blogs/kimberly/category/filtered-indexes/
De plus, le collègue de Kimberly, Joe Sack (@JosephSack), a déposé un élément Connect qui suggère de corriger ce comportement à la fois pour les index filtrés et les statistiques filtrées.
Limites des expressions de filtre
Il existe plusieurs constructions que vous ne pouvez pas utiliser dans un prédicat de filtre, telles que NOT IN
, OR
et des prédicats dynamiques / non déterministes comme WHERE col >= DATEADD(DAY, -1, GETDATE())
. De plus, l'optimiseur peut ne pas reconnaître un index filtré si le prédicat ne correspond pas exactement à WHERE
clause dans la définition de l'index. Voici quelques éléments Connect qui tentent d'obtenir une assistance pour une meilleure couverture :
L'index filtré n'autorise pas les filtres sur les disjonctions | (fermé :par conception) |
La création de l'index filtré a échoué avec la clause NOT IN | (fermé :par conception) |
Prise en charge d'une clause WHERE plus complexe dans les index filtrés | (actif) |
Autres utilisations potentielles actuellement impossibles
Nous ne pouvons actuellement pas créer d'index filtré sur une colonne calculée persistante, même si elle est déterministe. Nous ne pouvons pointer une clé étrangère vers un index filtré unique; si nous voulons qu'un index supporte la clé étrangère en plus des requêtes supportées par l'index filtré, nous devons créer un deuxième index non filtré et redondant. Et voici quelques autres limitations similaires qui ont été négligées ou qui n'ont pas encore été prises en compte :
Devrait être possible de créer un index filtré sur une colonne calculée persistante déterministe | (actif) |
Autoriser l'index unique filtré à être une clé candidate pour une clé étrangère | (actif) |
possibilité de créer des index de filtres sur les vues indexées | (fermé :ne résoudra pas) |
Erreur de partitionnement 1908 – Améliorer le partitionnement | (fermé :ne résoudra pas) |
CRÉER UN INDEX COLUMNSTORE "FILTRÉ" | (actif) |
Problèmes avec FUSION
Et MERGE
fait une énième apparition sur ma liste "à surveiller" :
MERGE évalue l'index filtré par ligne, et non après l'opération, ce qui provoque une violation de l'index filtré | (fermé :ne résoudra pas) |
MERGE ne parvient pas à se mettre à jour avec l'index filtré en place | (fermé :corrigé) |
Bogue de l'instruction MERGE lorsque INSERT/DELETE utilisait et filtrait l'index | (actif) |
MERGE signale à tort des violations de clés uniques | (actif) |
Bien que l'un de ces bogues (apparemment étroitement liés) indique qu'il est corrigé dans SQL Server 2012, vous devrez peut-être contacter PSS si vous rencontrez une variante de ce problème, en particulier sur les versions antérieures (ou arrêtez d'utiliser MERGE , comme je l'ai déjà suggéré).
Outil/DMV/limitations intégrées
Il existe de nombreux DMV, commandes DBCC, procédures système et outils clients sur lesquels nous commençons à nous appuyer au fil du temps. Cependant, toutes ces choses ne sont pas mises à jour pour tirer parti des nouvelles fonctionnalités; les index filtrés ne font pas exception. Les éléments Connect suivants signalent certains problèmes qui peuvent vous perturber si vous vous attendez à ce qu'ils fonctionnent avec des index filtrés :
Il n'y a aucun moyen de créer un index filtré à partir de SSMS lors de la conception d'une nouvelle table | (fermé :ne résoudra pas) |
L'expression de filtre d'un index filtré est perdue lorsqu'une table est modifiée par le Concepteur de table | (fermé :ne résoudra pas) |
Le concepteur de table n'écrit pas la clause WHERE dans les index filtrés | (actif) |
Le concepteur de table SSMS ne conserve pas l'expression de filtre d'index lors de la reconstruction de la table | (fermé :ne résoudra pas) |
DBCC PAGE sortie incorrecte avec des index filtrés | (actif) |
Suggestions d'index filtré SQL 2008 à partir des vues DM et DTA | (fermé :ne résoudra pas) |
Améliorations des index manquants DMV pour les index filtrés | (fermé :ne résoudra pas) |
Erreur de syntaxe lors de la réplication d'index filtrés compressés | (fermé :ne résoudra pas) |
Agent :les tâches utilisent des options autres que celles par défaut lors de l'exécution d'un script T-SQL | (fermé :ne résoudra pas) |
Afficher les dépendances échoue avec l'erreur Transact-SQL 515 | (actif) |
L'affichage des dépendances échoue sur certains objets | (fermé :ne résoudra pas) |
Les différences d'options d'index ne sont pas détectées dans la comparaison de schéma pour deux bases de données | (fermé :externe) |
Suggérer d'exposer la condition du filtre d'index dans toutes les vues des informations d'index | (fermé :ne résoudra pas) |
Les résultats de sp_helpIndex doivent inclure l'expression de filtre des indices de filtre | (actif) |
Surcharger sp_help, sp_columns, sp_helpindex pour les fonctionnalités 2008 | (fermé :ne résoudra pas) |
Pour les trois derniers, ne retenez pas votre souffle :il est peu probable que Microsoft investisse du temps dans les procédures sp_, les DMV, les vues INFORMATION_SCHEMA, etc. Consultez plutôt les réécritures sp_helpindex de Kimberly Tripp, qui incluent des informations sur les index filtrés ainsi que avec d'autres nouvelles fonctionnalités laissées par Microsoft.
Limites de l'Optimiseur
Plusieurs éléments Connect décrivent des cas où des index filtrés *pourraient* être utilisés par l'optimiseur, mais sont plutôt ignorés. Dans certains cas, ceux-ci ne sont pas considérés comme des "bugs" mais plutôt comme des "lacunes dans les fonctionnalités"…
SQL n'utilise pas d'index filtré sur une requête simple | (fermé :par conception) |
Le plan d'exécution de l'index filtré n'est pas optimisé | (fermé :ne résoudra pas) |
Index filtré non utilisé et recherche de clé sans sortie | (fermé :ne résoudra pas) |
L'utilisation de l'index filtré sur la colonne BIT dépend de l'expression SQL exacte utilisée dans la clause WHERE | (actif) |
La requête du serveur lié n'est pas correctement optimisée lorsqu'un index unique filtré existe | (fermé :ne résoudra pas) |
Row_Number() donne des résultats imprévisibles sur les serveurs liés où les index filtrés sont utilisés | (fermé :pas de reproduction) |
Index filtré évident non utilisé par QP | (fermé :par conception) |
Reconnaître les index filtrés uniques comme uniques | (actif) |
Paul White (@SQL_Kiwi) a récemment publié ici sur SQLPerformance.com un article qui décrit en détail quelques-unes des limitations de l'optimiseur ci-dessus.
Et Tim Chapman a écrit un excellent article décrivant certaines autres limitations des index filtrés, telles que l'impossibilité de faire correspondre le prédicat à une variable locale (corrigée dans 2008 R2 SP1) et l'impossibilité de spécifier un index filtré dans un indice d'index.
Conclusion
Les index filtrés ont un grand potentiel et j'avais de très grands espoirs pour eux lorsqu'ils ont été introduits pour la première fois dans SQL Server 2008. Cependant, la plupart des limitations fournies avec leur première version existent toujours aujourd'hui, un an et demi (ou deux, selon votre perspective) versions majeures plus tard. Ce qui précède semble être une liste assez longue d'éléments qui doivent être traités, mais je ne voulais pas que cela se présente de cette façon. Je veux juste que les gens soient conscients du grand nombre de problèmes potentiels qu'ils devront peut-être prendre en compte lorsqu'ils tireront parti des index filtrés.