Database
 sql >> Base de données >  >> RDS >> Database

Estimation du nombre de lignes à lire

Ne vous méprenez pas - j'adore la propriété Actual Rows Read que nous avons vue arriver dans les plans d'exécution de SQL Server à la fin de 2015. Mais dans SQL Server 2016 SP1, il y a moins de deux mois (et étant donné que nous avons eu Noël entre les deux, je ne pense pas que beaucoup de temps compte depuis), nous avons eu un autre ajout passionnant - Estimation du nombre de lignes à lire (oh, et c'est quelque peu dû à l'élément Connect que j'ai soumis, démontrant à la fois que les éléments Connect valent la peine d'être soumis et rendant ce message éligible pour le mardi T-SQL de ce mois-ci, organisé par Brent Ozar (@brento) sur le thème des éléments Connect ).

Récapitulons un instant… lorsque le moteur SQL accède aux données d'une table, il utilise soit une opération Scan, soit une opération Seek. Et à moins que Seek n'ait un prédicat Seek qui puisse accéder à au plus une ligne (car il recherche une correspondance d'égalité sur un ensemble de colonnes - pourrait être juste une seule colonne - qui sont connues pour être uniques), alors le Seek effectuera un RangeScan, et se comporte comme un Scan, juste à travers le sous-ensemble de lignes qui sont satisfaites par le Seek Predicate.

Les lignes satisfaites par un prédicat Seek (dans le cas d'un RangeScan d'une opération Seek) ou toutes les lignes de la table (dans le cas d'une opération Scan) sont traitées essentiellement de la même manière. Les deux peuvent être terminés plus tôt si plus aucune ligne n'est demandée à l'opérateur à sa gauche, par exemple si un opérateur supérieur quelque part a déjà saisi suffisamment de lignes, ou si un opérateur de fusion n'a plus de lignes à comparer. Et les deux peuvent être filtrés davantage par un prédicat résiduel (affiché comme la propriété "Prédicat") avant même que les lignes ne soient servies par l'opérateur Scan/Seek. Les propriétés "Nombre de lignes" et "Nombre estimé de lignes" nous indiqueraient combien de lignes devaient être produites par l'opérateur, mais nous n'avions aucune information sur la manière dont les lignes seraient filtrées uniquement par le prédicat Seek. Nous pouvions voir le TableCardiality, mais cela n'était vraiment utile que pour les opérateurs Scan, où il y avait une chance que le Scan parcoure toute la table pour les lignes dont il avait besoin. Ce n'était pas du tout utile pour Seeks.

La requête que j'exécute ici porte sur la base de données WideWorldImporters et est :

SELECT COUNT(*)
FROM Sales.Orders
WHERE SalespersonPersonID = 7
AND YEAR(OrderDate) = 2013
AND MONTH(OrderDate) = 4;

De plus, j'ai un index en jeu :

CREATE NONCLUSTERED INDEX rf_Orders_SalesPeople_OrderDate 
  ON Sales.Orders (SalespersonPersonID, OrderDate);

Cet index couvre - la requête n'a pas besoin d'autres colonnes pour obtenir sa réponse - et a été conçu pour qu'un prédicat Seek puisse être utilisé sur SalespersonPersonID, filtrant rapidement les données vers une plage plus petite. Les fonctions sur OrderDate signifient que ces deux derniers prédicats ne peuvent pas être utilisés dans le prédicat Seek, ils sont donc relégués au prédicat résiduel à la place. Une meilleure requête filtrerait ces dates en utilisant OrderDate>='20130401' AND OrderDate <'20130501', mais j'imagine ici un scénario qui n'est que trop courant…

Maintenant, si j'exécute la requête, je peux voir l'impact des prédicats résiduels. Plan Explorer donne même cet avertissement utile sur lequel j'avais déjà écrit.

Je peux voir très clairement que le RangeScan est de 7 276 lignes et que le prédicat résiduel le filtre jusqu'à 149. Plan Explorer affiche plus d'informations à ce sujet dans l'info-bulle :

Mais sans exécuter la requête, je ne peux pas voir ces informations. Ce n'est tout simplement pas là. Les propriétés du plan estimé ne l'ont pas :

Et je suis sûr que je n'ai pas besoin de vous le rappeler - cette information n'est pas non plus présente dans le cache du plan. Après avoir récupéré le plan du cache en utilisant :

SELECT p.query_plan, t.text
FROM sys.dm_exec_cached_plans c
CROSS APPLY sys.dm_exec_query_plan(c.plan_handle) p
CROSS APPLY sys.dm_exec_sql_text(c.plan_handle) t
WHERE t.text LIKE '%YEAR%';

Je l'ai ouvert, et bien sûr, aucun signe de cette valeur de 7 276. Il ressemble exactement au plan estimé que je viens de montrer.

C'est en extrayant les plans du cache que les valeurs estimées prennent tout leur sens. Ce n'est pas seulement que je préférerais ne pas exécuter de requêtes potentiellement coûteuses sur les bases de données clients. Interroger le cache du plan est une chose, mais exécuter des requêtes pour obtenir les chiffres réels, c'est beaucoup plus difficile.

Avec SQL 2016 SP1 installé, grâce à cet élément Connect, je peux maintenant voir la propriété Nombre estimé de lignes à lire dans les plans estimés et dans le cache du plan. L'info-bulle de l'opérateur affichée ici est extraite du cache, et je peux facilement voir cette propriété estimée indiquant 7 276, ainsi que l'avertissement résiduel :

C'est quelque chose que je pourrais faire sur une boîte client, en cherchant dans le cache des situations dans des plans problématiques où le rapport entre le nombre estimé de lignes à lire et le nombre estimé de lignes n'est pas excellent. Potentiellement, quelqu'un pourrait créer un processus qui vérifie chaque plan dans le cache, mais ce n'est pas quelque chose que j'ai fait.

Une lecture astucieuse aura remarqué que les lignes réelles qui sont sorties de cet opérateur étaient de 149, ce qui était beaucoup plus petit que les 1382,56 estimés. Mais lorsque je recherche des prédicats résiduels qui doivent vérifier trop de lignes, le rapport de 1 382,56 :7 276 est toujours significatif.

Maintenant que nous avons constaté que cette requête est inefficace sans même avoir besoin de l'exécuter, la solution consiste à s'assurer que le prédicat résiduel est suffisamment SARGable. Cette requête…

SELECT COUNT(*) 
FROM Sales.Orders
WHERE SalespersonPersonID = 7 
AND OrderDate >= '20130401' 
AND OrderDate <  '20130501';

… donne les mêmes résultats et n'a pas de prédicat résiduel. Dans cette situation, la valeur Estimation du nombre de lignes à lire est identique à l'Estimation du nombre de lignes, et l'inefficacité a disparu :

Comme mentionné précédemment, cet article fait partie du mardi T-SQL de ce mois-ci. Pourquoi ne pas y aller pour voir quelles autres demandes de fonctionnalités ont été accordées récemment ?