SQL Server met-il en cache le résultat d'une fonction table à instructions multiples?

22

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?

Paul White dit GoFundMonica
la source

Réponses:

23

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é.

Exemple msTVF

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;

Variable de table statique

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 à:

Résultat simple

Le plan d'exécution est le suivant:

Plan d'exécution simple

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.

Accès multiples

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:

Joindre le résultat

Le plan d'exécution:

Rejoignez le plan

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.

Paramètres corrélés et paramètres non constants

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 Tafin 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 Tdans 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:

Résultat corrélé

Notez la différence de temps entre les lignes de la colonne ts. La WHEREclause 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 ifrom table T).

Le plan d'exécution est le suivant:

Plan d'exécution corrélé

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 iet supprimerait la WHEREclause 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 ivaleurs minimale et maximale Tet 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.

Mise en cache des paramètres corrélés inchangés

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 Tpar 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 :

Résultats de ligne identiques

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 iest 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:

Planifier des lignes identiques

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:

Propriétés de l'opérateur

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.iest inchangée.

Paul White dit GoFundMonica
la source