Une fonction table multi-instructions renvoie son résultat dans une variable de table.
Ces résultats sont-ils jamais réutilisés ou la fonction est-elle toujours pleinement évaluée à chaque appel?
Une fonction table multi-instructions renvoie son résultat dans une variable de table.
Ces résultats sont-ils jamais réutilisés ou la fonction est-elle toujours pleinement évaluée à chaque appel?
Réponses:
Les résultats d'une fonction table multi-instructions (msTVF) ne sont jamais mis en cache ou réutilisés sur des instructions (ou connexions), mais il existe plusieurs façons de réutiliser un résultat msTVF dans la même instruction. Dans cette mesure, un msTVF n'est pas nécessairement repeuplé à chaque fois qu'il est appelé.
Ce msTVF (délibérément inefficace) renvoie une plage spécifiée d'entiers, avec un horodatage sur chaque ligne:
IF OBJECT_ID(N'dbo.IntegerRange', 'TF') IS NOT NULL
DROP FUNCTION dbo.IntegerRange;
GO
CREATE FUNCTION dbo.IntegerRange (@From integer, @To integer)
RETURNS @T table
(
n integer PRIMARY KEY,
ts datetime DEFAULT CURRENT_TIMESTAMP
)
WITH SCHEMABINDING
AS
BEGIN
WHILE @From <= @To
BEGIN
INSERT @T (n)
VALUES (@From);
SET @From = @From + 1;
END;
RETURN;
END;
Si tous les paramètres de l'appel de fonction sont des constantes (ou constantes d'exécution), le plan d'exécution remplira une fois le résultat de la variable de table. Le reste du plan peut accéder à la variable de table plusieurs fois. La nature statique de la variable de table peut être reconnue à partir du plan d'exécution. Par exemple:
SELECT
IR.n,
IR.ts
FROM dbo.IntegerRange(1, 5) AS IR
ORDER BY
IR.n;
Renvoie un résultat similaire à:
Le plan d'exécution est le suivant:
L'opérateur Sequence appelle d'abord l'opérateur Table Valued Function, qui remplit la variable de table (notez que cet opérateur ne renvoie aucune ligne). Ensuite, la séquence appelle sa deuxième entrée, qui renvoie le contenu de la variable de table (en utilisant un balayage d'index clusterisé dans ce cas).
Le résultat selon lequel le plan utilise un résultat de variable de tableau «statique» est l'opérateur de fonction de valeur de table sous une séquence - la variable de tableau doit être entièrement remplie une fois avant que le reste du plan puisse démarrer.
Pour afficher le résultat de la variable de table accédée plusieurs fois, nous utiliserons une deuxième table avec des lignes numérotées de 1 à 5:
IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL
DROP TABLE dbo.T;
CREATE TABLE dbo.T (i integer NOT NULL);
INSERT dbo.T (i)
VALUES (1), (2), (3), (4), (5);
Et une nouvelle requête qui joint cette table à notre fonction (cela pourrait également être écrit comme un APPLY
):
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
JOIN dbo.IntegerRange(1, 5) AS IR
ON IR.n = T.i;
Le résultat est:
Le plan d'exécution:
Comme précédemment, la séquence remplit d'abord le résultat de la variable de table msTVF. Ensuite, des boucles imbriquées sont utilisées pour joindre chaque ligne du tableau T
à une ligne du résultat msTVF. Étant donné que la définition de la fonction comprenait un index utile sur la variable de table, une recherche d'index peut être utilisée.
Le point clé est que lorsque les paramètres du msTVF sont des constantes (y compris des variables et des paramètres) ou traités comme des constantes d'exécution pour l'instruction par le moteur d'exécution, le plan comportera deux opérateurs distincts pour le résultat de la variable de la table msTVF: un pour remplir le table; un autre pour accéder aux résultats, éventuellement accéder plusieurs fois à la table, et éventuellement utiliser des index déclarés dans la définition de la fonction.
Pour mettre en évidence les différences lorsque des paramètres corrélés (références externes) ou des paramètres de fonction non constants sont utilisés, nous allons modifier le contenu du tableau T
afin que la fonction ait beaucoup plus de travail à faire:
TRUNCATE TABLE dbo.T;
INSERT dbo.T (i)
VALUES (50001), (50002), (50003), (50004), (50005);
La requête modifiée suivante utilise désormais une référence externe à table T
dans l'un des paramètres de fonction:
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;
Cette requête prend environ 8 secondes pour renvoyer des résultats tels que:
Notez la différence de temps entre les lignes de la colonne ts
. La WHERE
clause limite le résultat final pour une sortie de taille raisonnable, mais la fonction inefficace prend encore un certain temps pour remplir la variable de table avec 50 000 lignes impaires (en fonction de la valeur corrélée de i
from table T
).
Le plan d'exécution est le suivant:
Notez l'absence d'un opérateur de séquence. Désormais, il existe un seul opérateur Table Valued Function qui remplit la variable de table et renvoie ses lignes à chaque itération de la jointure de boucles imbriquées.
Pour être clair: avec seulement 5 lignes dans la table T, l'opérateur Table Valued Function s'exécute 5 fois. Il génère 50 001 lignes sur la première itération, 50 002 sur la seconde ... et ainsi de suite. La variable de table est «jetée» (tronquée) entre les itérations, donc chacun des cinq appels est une population complète. C'est pourquoi il est si lent et chaque ligne met environ le même temps pour apparaître dans le résultat.
Notes annexes:
Naturellement, le scénario ci-dessus est délibérément conçu pour montrer à quel point les performances peuvent être médiocres lorsque le msTVF remplit de nombreuses lignes à chaque itération.
Une implémentation judicieuse du code ci-dessus définirait les deux paramètres msTVF sur i
et supprimerait la WHERE
clause redondante . La variable de table serait toujours tronquée et repeuplée à chaque itération, mais uniquement avec une ligne à chaque fois.
Nous pourrions également extraire les i
valeurs minimale et maximale T
et les stocker dans des variables lors d'une étape précédente. L'appel de la fonction avec des variables au lieu de paramètres corrélés permettrait d'utiliser le modèle de variable de table «statique» comme indiqué précédemment.
De retour pour répondre à la question d'origine, où le modèle statique de séquence ne peut pas être utilisé, SQL Server peut éviter de tronquer et de repeupler la variable de table msTVF si aucun des paramètres corrélés n'a changé depuis l'itération précédente d'une jointure de boucle imbriquée.
Pour le démontrer, nous remplacerons le contenu de T
par cinq valeurs identiques i
:
TRUNCATE TABLE dbo.T;
INSERT dbo.T (i)
VALUES (50005), (50005), (50005), (50005), (50005);
La requête avec un paramètre corrélé à nouveau:
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;
Cette fois, les résultats apparaissent dans environ 1,5 seconde :
Notez les horodatages identiques sur chaque ligne. Le résultat mis en cache dans la variable de table est réutilisé pour les itérations suivantes où la valeur corrélée i
est inchangée. La réutilisation du résultat est beaucoup plus rapide que l'insertion de 50 005 lignes à chaque fois.
Le plan d'exécution ressemble beaucoup au précédent:
La principale différence réside dans les propriétés Réaffectations réelles et Rebobines réelles de l'opérateur Fonction de valeur de table:
Lorsque les paramètres corrélés ne changent pas, SQL Server peut relire (rembobiner) les résultats actuels dans la variable de table. Lorsque la corrélation change, SQL Server doit tronquer et repeupler la variable de table (relier). La première liaison se produit à la première itération; les quatre itérations suivantes sont toutes des rembobinages puisque la valeur de T.i
est inchangée.