Chaque fois que je rencontre ce type de requêtes, je me demande toujours comment SQL Server fonctionnerait. Si j'exécute un type de requête qui nécessite un calcul et que j'utilise ensuite cette valeur à plusieurs endroits, par exemple dans le select
et le order by
, SQL Server le calculera-t-il deux fois pour chaque ligne ou sera-t-il mis en cache? De plus, comment cela fonctionne-t-il avec les fonctions définies par l'utilisateur?
Exemples:
SELECT CompanyId, Count(*)
FROM Sales
ORDER BY Count(*) desc
SELECT Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STX, Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STY
FROM Table
SELECT Id, udf.MyFunction(Id)
FROM Table
ORDER BY udf.MyFunction(Id)
Existe-t-il un moyen de le rendre plus efficace ou SQL Server est-il suffisamment intelligent pour le gérer pour moi?
sql-server
Jonas Stawski
la source
la source
SELECT RAND() FROM Sales order by RAND()
- ceci n'est évalué qu'une seule fois car il est à la fois non déterministe et une constante de temps d'exécution.Réponses:
L'optimiseur de requêtes SQL Server peut combiner des valeurs calculées répétées en un seul opérateur de calcul scalaire. Le fait de le faire ou non dépend du coût du plan de requête et des propriétés de la valeur calculée. Comme prévu, il ne le fera pas pour les valeurs calculées qui ne sont pas déterministes, à quelques exceptions près comme
RAND()
. Il ne le fera pas non plus pour les fonctions définies par l'utilisateur.Je vais commencer par un exemple de fonction définie par l'utilisateur. Voici un excellent exemple de fonction définie par l'utilisateur:
Je veux aussi créer un tableau et y mettre 100 lignes:
La
dbo.NULL_FUNCTION
fonction est déterministe. Combien de fois sera-t-il exécuté pour la requête suivante?En fonction du plan de requête, cela sera exécuté une fois pour chaque ligne, ou 100 fois:
SQL Server 2016 a introduit le DMV sys.dm_exec_function_stats . Nous pouvons prendre des instantanés de ce DMV pour voir combien de fois un UDF est exécuté par une requête.
Le résultat est 100, donc la fonction a été exécutée 100 fois.
Essayons une autre requête simple:
Le plan de requête suggère que la fonction sera exécutée 200 fois:
Les résultats de
sys.dm_exec_function_stats
suggèrent que la fonction a été exécutée 200 fois.Notez que vous ne pouvez pas toujours utiliser le plan de requête pour déterminer combien de fois un scalaire de calcul est exécuté. La citation suivante est extraite de " Calculer les scalaires, les expressions et les performances du plan d'exécution ":
Essayons un autre exemple. Pour la requête suivante, j'espère que l'UDF est calculé une fois:
Le plan de requête suggère qu'il sera calculé une seule fois:
Cependant, le DMV révèle la vérité. Le scalaire de calcul est différé jusqu'à ce qu'il soit nécessaire, ce qui se trouve dans l'opérateur de jointure. Il est évalué 100 fois.
Vous avez également demandé ce que vous pouvez faire pour encourager l'optimiseur à éviter de recalculer plusieurs fois la même expression. La meilleure chose que vous puissiez faire est d'éviter d'utiliser des FDU scalaires dans votre code. Ceux-ci présentent un certain nombre de problèmes de performances en dehors de cette question, notamment le gonflement des allocations de mémoire, le forçage de la requête entière à exécuter
MAXDOP 1
, de mauvaises estimations de cardinalité et une utilisation supplémentaire du processeur. Si vous devez utiliser un UDF et que la valeur de cet UDF est une constante, vous pouvez le calculer en dehors de la requête et le placer dans une variable locale.Pour les requêtes sans UDF, vous pouvez essayer d'éviter d'écrire des expressions qui retournent le même résultat mais ne sont pas tapées exactement de la même manière. Pour cet exemple suivant, j'utilise la base de données AdventureworksDW2016CTP3 accessible au public, mais vraiment n'importe quelle base de données fera l'affaire. Combien de fois sera
COUNT(*)
calculé pour cette requête?Pour cette requête, nous pouvons comprendre cela en regardant l'opérateur Hash Match (agrégat).
Le
COUNT(*)
est calculé une fois pour chaque valeur unique deOrderDateKey
. L'inclusion de laORDER BY
clause ne la fait pas être calculée deux fois. Vous pouvez voir le plan d'exécution ici .Maintenant, considérez une requête qui retournera exactement les mêmes résultats mais qui est écrite d'une manière différente:
L'optimiseur de requêtes n'est pas assez intelligent pour les combiner, donc un travail supplémentaire sera effectué:
la source