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

Principes de base des expressions de table, partie 13 - Fonctions de table en ligne, suite

Ceci est le treizième et dernier volet d'une série sur les expressions de table. Ce mois-ci, je poursuis la discussion que j'ai commencée le mois dernier sur les fonctions de table en ligne (iTVF).

Le mois dernier, j'ai expliqué que lorsque SQL Server intègre des iTVF interrogés avec des constantes en entrée, il applique l'optimisation d'intégration de paramètres par défaut. L'incorporation de paramètres signifie que SQL Server remplace les références de paramètres dans la requête par les valeurs constantes littérales de l'exécution en cours, puis le code avec les constantes est optimisé. Ce processus permet des simplifications qui peuvent aboutir à des plans de requête plus optimaux. Ce mois-ci, j'élabore sur le sujet, couvrant des cas spécifiques pour de telles simplifications telles que le pliage constant et le filtrage et l'ordre dynamiques. Si vous avez besoin d'un rappel sur l'optimisation de l'intégration des paramètres, consultez l'article du mois dernier ainsi que l'excellent article de Paul White Parameter Sniffing, Embedding, and the RECOMPILE Options.

Dans mes exemples, j'utiliserai un exemple de base de données appelé TSQLV5. Vous pouvez trouver le script qui le crée et le remplit ici et son diagramme ER ici.

Pliage constant

Au cours des premières étapes du traitement des requêtes, SQL Server évalue certaines expressions impliquant des constantes, en repliant celles-ci sur les constantes de résultat. Par exemple, l'expression 40 + 2 peut être pliée en la constante 42. Vous pouvez trouver les règles pour les expressions pliables et non pliables ici sous "Constant Folding and Expression Evaluation".

Ce qui est intéressant en ce qui concerne les iTVF, c'est que grâce à l'optimisation de l'intégration des paramètres, les requêtes impliquant des iTVF où vous transmettez des constantes en tant qu'entrées peuvent, dans les bonnes circonstances, bénéficier d'un repliement constant. Connaître les règles pour les expressions pliables et non pliables peut affecter la façon dont vous implémentez vos iTVF. Dans certains cas, en appliquant des modifications très subtiles à vos expressions, vous pouvez activer des plans plus optimaux avec une meilleure utilisation de l'indexation.

À titre d'exemple, considérons l'implémentation suivante d'un iTVF appelé Sales.MyOrders :

USE TSQLV5;GO CREATE OR ALTER FUNCTION Sales.MyOrders ( @add AS INT, @subtract AS INT )RETURNS TABLEASRETURN SELECT orderid + @add - @subtract AS myorderid, orderdate, custid, empid FROM Sales.Orders;GO 

Émettez la requête suivante impliquant l'iTVF (je l'appellerai requête 1) :

SELECT myorderid, orderdate, custid, empidFROM Sales.MyOrders(1, 10248)ORDER BY myorderid ;

Le plan pour la requête 1 est illustré à la figure 1.

Figure 1 :Plan pour la requête 1

L'index clusterisé PK_Orders est défini avec orderid comme clé. Si un pliage constant avait eu lieu ici après l'intégration des paramètres, l'expression d'ordre orderid + 1 – 10248 aurait été pliée en orderid – 10247. Cette expression aurait été considérée comme une expression préservant l'ordre par rapport à orderid, et en tant que telle aurait permis à la optimiseur pour s'appuyer sur l'ordre de l'index. Hélas, ce n'est pas le cas, comme en témoigne l'opérateur Sort explicite dans le plan. Alors, qu'est-ce-qu'il s'est passé?

Les règles de pliage constantes sont capricieuses. L'expression colonne1 + constante1 – constante2 est évaluée de gauche à droite à des fins de pliage constant. La première partie, colonne1 + constante1 n'est pas pliée. Appelons cette expression1. La partie suivante qui est évaluée est traitée comme expression1 – constant2, qui n'est pas repliée non plus. Sans pliage, une expression sous la forme colonne1 + constante1 – constante2 n'est pas considérée comme préservant l'ordre par rapport à la colonne1 et ne peut donc pas s'appuyer sur l'ordre des index même si vous avez un index de support sur la colonne1. De même, l'expression constante1 + colonne1 – constante2 n'est pas constante pliable. Cependant, l'expression constant1 – constant2 + column1 est pliable. Plus précisément, la première partie constante1 - constante2 est pliée en une seule constante (appelons-la constante3), ce qui donne l'expression constante3 + colonne1. Cette expression est considérée comme une expression préservant l'ordre par rapport à la colonne1. Ainsi, tant que vous vous assurez d'écrire votre expression en utilisant la dernière forme, vous pouvez permettre à l'optimiseur de s'appuyer sur l'ordre des index.

Considérez les requêtes suivantes (je les appellerai Requête 2, Requête 3 et Requête 4), et avant d'examiner les plans de requête, voyez si vous pouvez dire laquelle impliquera un tri explicite dans le plan et laquelle ne le fera pas :

-- Requête 2SELECT orderid + 1 - 10248 AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid ; -- Requête 3SELECT 1 + orderid - 10248 AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid; -- Requête 4SELECT 1 - 10248 + orderid AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid;

Examinez maintenant les plans de ces requêtes, comme illustré à la figure 2.

Figure 2 :Plans pour la requête 2, la requête 3 et la requête 4

Examinez les opérateurs Compute Scalar dans les trois plans. Seul le plan de la requête 4 entraînait un pliage constant, ce qui entraînait une expression de tri considérée comme préservant l'ordre par rapport à orderid, évitant un tri explicite.

En comprenant cet aspect du pliage constant, vous pouvez facilement corriger l'iTVF en changeant l'expression orderid + @add – @subtract en @add – @subtract + orderid, comme ceci :

CREATE OR ALTER FUNCTION Sales.MyOrders ( @add AS INT, @subtract AS INT )RETURNS TABLEASRETURN SELECT @add - @subtract + orderid AS myorderid, orderdate, custid, empid FROM Sales.Orders;GO

Interrogez à nouveau la fonction (je l'appellerai Requête 5) :

SELECT myorderid, orderdate, custid, empidFROM Sales.MyOrders(1, 10248)ORDER BY myorderid ;

Le plan de cette requête est illustré à la figure 3.

Figure 3 :Plan pour la requête 5

Comme vous pouvez le constater, cette fois, la requête a subi un pliage constant et l'optimiseur a pu s'appuyer sur l'ordre des index, évitant ainsi le tri explicite.

J'ai utilisé un exemple simple pour démontrer cette technique d'optimisation, et en tant que telle, cela peut sembler un peu artificiel. Vous pouvez trouver une application pratique de cette technique dans l'article Solutions du défi du générateur de séries de nombres - Partie 1.

Filtrage/Classement dynamique

Le mois dernier, j'ai couvert la différence entre la façon dont SQL Server optimise une requête dans un iTVF par rapport à la même requête dans une procédure stockée. SQL Server applique généralement l'optimisation par incorporation de paramètres par défaut pour une requête impliquant un iTVF avec des constantes comme entrées, mais optimise la forme paramétrée d'une requête dans une procédure stockée. Toutefois, si vous ajoutez OPTION(RECOMPILE) à la requête dans la procédure stockée, SQL Server appliquera généralement l'optimisation d'incorporation de paramètres dans ce cas également. Les avantages dans le cas iTVF incluent le fait que vous pouvez l'impliquer dans une requête, et tant que vous passez des entrées constantes répétées, il est possible de réutiliser un plan précédemment mis en cache. Avec une procédure stockée, vous ne pouvez pas l'impliquer dans une requête, et si vous ajoutez OPTION(RECOMPILE) pour obtenir l'optimisation de l'incorporation de paramètres, il n'y a aucune possibilité de réutilisation du plan. La procédure stockée permet beaucoup plus de flexibilité en termes d'éléments de code que vous pouvez utiliser.

Voyons comment tout cela se déroule dans une tâche classique d'incorporation et de commande de paramètres. Voici une procédure stockée simplifiée qui applique un filtrage et un tri dynamiques similaires à celui utilisé par Paul dans son article :

CRÉER OU MODIFIER LA PROCÉDURE HR.GetEmpsP @lastnamepattern AS NVARCHAR(50), @sort AS TINYINTASSET NOCOUNT ON ; SELECT empid, firstname, lastnameFROM HR.EmployeesWHERE lastname LIKE @lastnamepattern OR @lastnamepattern IS NULLORDER BY CASE WHEN @sort =1 THEN empid END, CASE WHEN @sort =2 THEN firstname END, CASE WHEN @sort =3 THEN lastname END;GO 

Notez que l'implémentation actuelle de la procédure stockée n'inclut pas OPTION(RECOMPILE) dans la requête.

Considérez l'exécution suivante de la procédure stockée :

EXEC HR.GetEmpsP @lastnamepattern =N'D%', @sort =3 ;

Le plan de cette exécution est illustré à la figure 4.

Figure 4 :Plan pour la procédure HR.GetEmpsP

Il y a un index défini sur la colonne lastname. Théoriquement, avec les entrées actuelles, l'index pourrait être bénéfique à la fois pour le filtrage (avec une recherche) et le classement (avec un balayage ordonné :vraie plage) de la requête. Cependant, étant donné que SQL Server optimise par défaut la forme paramétrée de la requête et n'applique pas d'incorporation de paramètres, il n'applique pas les simplifications nécessaires pour pouvoir bénéficier de l'index à des fins de filtrage et de classement. Ainsi, le plan est réutilisable, mais il n'est pas optimal.

Pour voir comment les choses changent avec l'optimisation de l'intégration des paramètres, modifiez la requête de la procédure stockée en ajoutant OPTION(RECOMPILE), comme ceci :

CRÉER OU MODIFIER LA PROCÉDURE HR.GetEmpsP @lastnamepattern AS NVARCHAR(50), @sort AS TINYINTASSET NOCOUNT ON ; SELECT empid, firstname, lastnameFROM HR.EmployeesWHERE lastname LIKE @lastnamepattern OR @lastnamepattern IS NULLORDER BY CASE WHEN @sort =1 THEN empid END, CASE WHEN @sort =2 THEN firstname END, CASE WHEN @sort =3 THEN lastname ENDOPTION(RECOMPILE ); ALLER

Exécutez à nouveau la procédure stockée avec les mêmes entrées que vous avez utilisées auparavant :

EXEC HR.GetEmpsP @lastnamepattern =N'D%', @sort =3 ;

Le plan de cette exécution est illustré à la figure 5.

Figure 5 :Plan pour la procédure HR.GetEmpsP avec OPTION(RECOMPILE)

Comme vous pouvez le voir, grâce à l'optimisation de l'intégration des paramètres, SQL Server a pu simplifier le prédicat de filtre en prédicat sargable lastname LIKE N'D%' et la liste de classement en NULL, NULL, lastname. Les deux éléments pourraient bénéficier de l'index sur le nom de famille, et donc le plan montre une recherche dans l'index et aucun tri explicite.

Théoriquement, vous vous attendez à pouvoir obtenir une simplification similaire si vous implémentez la requête dans un iTVF, et donc des avantages d'optimisation similaires, mais avec la possibilité de réutiliser des plans mis en cache lorsque les mêmes valeurs d'entrée sont réutilisées. Alors, essayons…

Voici une tentative d'implémentation de la même requête dans un iTVF (n'exécutez pas encore ce code) :

CREATE OR ALTER FUNCTION HR.GetEmpsF( @lastnamepattern AS NVARCHAR(50), @sort AS TINYINT)RETURNS TABLEASRETURN SELECT empid, firstname, lastname FROM HR.Employees WHERE lastname LIKE @lastnamepattern OR @lastnamepattern IS NULL ORDER BY CASE WHEN @sort =1 ALORS empid FIN, CAS QUAND @sort =2 ALORS prénom FIN, CAS QUAND @sort =3 ALORS nom FIN;GO

Avant d'essayer d'exécuter ce code, voyez-vous un problème avec cette implémentation ? Rappelez-vous qu'au début de cette série, j'ai expliqué qu'une expression de table est une table. Le corps d'une table est un ensemble (ou multi-ensemble) de lignes, et en tant que tel n'a pas d'ordre. Par conséquent, normalement, une requête utilisée comme expression de table ne peut pas avoir de clause ORDER BY. En effet, si vous essayez d'exécuter ce code, vous obtenez l'erreur suivante :

Msg 1033, Niveau 15, État 1, Procédure GetEmps, Ligne 16 [Batch Start Line 128]
La clause ORDER BY n'est pas valide dans les vues, les fonctions en ligne, les tables dérivées, les sous-requêtes et les expressions de table communes, sauf TOP, OFFSET ou FOR XML est également spécifié.

Bien sûr, comme le dit l'erreur, SQL Server fera une exception si vous utilisez un élément de filtrage comme TOP ou OFFSET-FETCH, qui s'appuie sur la clause ORDER BY pour définir l'aspect de l'ordre du filtre. Mais même si vous incluez une clause ORDER BY dans la requête interne grâce à cette exception, vous n'obtenez toujours pas de garantie pour l'ordre du résultat dans une requête externe par rapport à l'expression de table, à moins qu'elle n'ait sa propre clause ORDER BY .

Si vous souhaitez toujours implémenter la requête dans un iTVF, vous pouvez faire en sorte que la requête interne gère la partie de filtrage dynamique, mais pas l'ordre dynamique, comme ceci :

CREATE OR ALTER FUNCTION HR.GetEmpsF( @lastnamepattern AS NVARCHAR(50))RETURNS TABLEASRETURN SELECT empid, firstname, lastname FROM HR.Employees WHERE lastname LIKE @lastnamepattern OR @lastnamepattern IS NULL;GO

Bien sûr, vous pouvez faire en sorte que la requête externe gère tout besoin de commande spécifique, comme dans le code suivant (je l'appellerai la requête 6) :

SELECT empid, prénom, nomFROM HR.GetEmpsF(N'D%')ORDER BY nom;

Le plan de cette requête est illustré à la figure 6.

Figure 6 :Plan pour la requête 6

Grâce à l'intégration et à l'incorporation de paramètres, le plan est similaire à celui présenté précédemment pour la requête de procédure stockée de la figure 5. Le plan s'appuie efficacement sur l'index à la fois pour le filtrage et le classement. Cependant, vous n'obtenez pas la flexibilité de l'entrée de commande dynamique comme vous l'aviez avec la procédure stockée. Vous devez être explicite avec l'ordre dans la clause ORDER BY dans la requête sur la fonction.

L'exemple suivant a une requête sur la fonction sans filtrage ni exigence de classement (je l'appellerai Requête 7) :

SELECT empid, prénom, nomFROM HR.GetEmpsF(NULL);

Le plan de cette requête est illustré à la figure 7.

Figure 7 :Plan pour la requête 7

Après l'intégration et l'intégration des paramètres, la requête est simplifiée pour ne pas avoir de prédicat de filtre ni d'ordre, et est optimisée avec une analyse complète non ordonnée de l'index clusterisé.

Enfin, interrogez la fonction avec N'D%' comme modèle de filtrage du nom de famille d'entrée et ordonnez le résultat par la colonne du prénom (je l'appellerai Requête 8) :

SELECT empid, firstname, lastnameFROM HR.GetEmpsF(N'D%')ORDER BY firstname ;

Le plan de cette requête est illustré à la figure 8.

Figure 8 :Plan pour la requête 8

Après simplification, la requête porte uniquement sur le prédicat de filtrage lastname LIKE N'D%' et l'élément de tri firstname. Cette fois, l'optimiseur choisit d'appliquer un parcours non ordonné de l'index clusterisé, avec le prédicat résiduel lastname LIKE N'D%', suivi d'un tri explicite. Il a choisi de ne pas appliquer de recherche dans l'index sur lastname car l'index n'est pas un index de couverture, la table est si petite et l'ordre d'index n'est pas bénéfique pour les besoins actuels d'ordre des requêtes. De plus, il n'y a pas d'index défini sur la colonne firstname, donc un tri explicite doit être appliqué de toute façon.

Conclusion

L'optimisation par défaut de l'intégration des paramètres des iTVF peut également entraîner un repliement constant, permettant des plans plus optimaux. Cependant, vous devez être conscient des règles de pliage constantes pour déterminer la meilleure façon de formuler vos expressions.

L'implémentation de la logique dans un iTVF présente des avantages et des inconvénients par rapport à l'implémentation de la logique dans une procédure stockée. Si vous n'êtes pas intéressé par l'optimisation de l'incorporation de paramètres, l'optimisation des requêtes paramétrées par défaut des procédures stockées peut entraîner une mise en cache et un comportement de réutilisation du plan plus optimaux. Dans les cas où vous êtes intéressé par l'optimisation par intégration de paramètres, vous l'obtenez généralement par défaut avec les iTVF. Pour obtenir cette optimisation avec les procédures stockées, vous devez ajouter l'option de requête RECOMPILE, mais vous n'obtiendrez pas la réutilisation du plan. Au moins avec les iTVF, vous pouvez obtenir une réutilisation du plan à condition que les mêmes valeurs de paramètre soient répétées. Là encore, vous avez moins de flexibilité avec les éléments de requête que vous pouvez utiliser dans un iTVF ; par exemple, vous n'êtes pas autorisé à avoir une clause ORDER BY de présentation.

Pour en revenir à toute la série sur les expressions de table, je trouve que le sujet est super important pour les praticiens des bases de données. La série la plus complète comprend la sous-série sur le générateur de séries de nombres, qui est implémenté en tant qu'iTVF. Au total, la série comprend les 19 parties suivantes :

  • Principes de base des expressions de table, partie 1
  • Principes de base des expressions de table, partie 2 – Tables dérivées, considérations logiques
  • Principes de base des expressions de table, partie 3 – Tables dérivées, considérations d'optimisation
  • Principes de base des expressions de table, Partie 4 – Tables dérivées, considérations d'optimisation, suite
  • Principes de base des expressions de table, partie 5 – CTE, considérations logiques
  • Principes de base des expressions de table, partie 6 :CTE récursifs
  • Principes de base des expressions de table, partie 7 – CTE, considérations d'optimisation
  • Principes de base des expressions de table, partie 8 :CTE, considérations d'optimisation (suite)
  • Principes de base des expressions de table, partie 9 – Vues, ​​comparées aux tables dérivées et aux CTE
  • Principes de base des expressions de table, Partie 10 :Vues, ​​SELECT * et modifications DDL
  • Principes de base des expressions de table, partie 11 – Vues, ​​considérations de modification
  • Principes de base des expressions de table, partie 12 – Fonctions de table en ligne
  • Principes de base des expressions de table, partie 13 – Fonctions de table en ligne, suite
  • Le défi est lancé ! Appel communautaire pour la création du générateur de séries de nombres le plus rapide
  • Solutions du défi du générateur de séries de nombres – Partie 1
  • Solutions du défi du générateur de séries de nombres – Partie 2
  • Solutions du défi du générateur de séries de nombres – Partie 3
  • Solutions du défi du générateur de séries de nombres – Partie 4
  • Solutions du défi du générateur de séries de nombres – Partie 5