Fonction de valeur de table à instructions multiples vs fonction de valeur de table en ligne

198

Quelques exemples à montrer, au cas où:

Table en ligne valorisée

CREATE FUNCTION MyNS.GetUnshippedOrders()
RETURNS TABLE
AS 
RETURN SELECT a.SaleId, a.CustomerID, b.Qty
    FROM Sales.Sales a INNER JOIN Sales.SaleDetail b
        ON a.SaleId = b.SaleId
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.ShipDate IS NULL
GO

Tableau d'instructions multiples évalué

CREATE FUNCTION MyNS.GetLastShipped(@CustomerID INT)
RETURNS @CustomerOrder TABLE
(SaleOrderID    INT         NOT NULL,
CustomerID      INT         NOT NULL,
OrderDate       DATETIME    NOT NULL,
OrderQty        INT         NOT NULL)
AS
BEGIN
    DECLARE @MaxDate DATETIME

    SELECT @MaxDate = MAX(OrderDate)
    FROM Sales.SalesOrderHeader
    WHERE CustomerID = @CustomerID

    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a INNER JOIN Sales.SalesOrderHeader b
        ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.OrderDate = @MaxDate
        AND a.CustomerID = @CustomerID
    RETURN
END
GO

Y a-t-il un avantage à utiliser un type (en ligne ou multi-instruction) par rapport à l'autre? Y a-t-il certains scénarios où l'un est meilleur que l'autre ou les différences sont-elles purement syntaxiques? Je me rends compte que les deux exemples de requêtes font des choses différentes, mais y a-t-il une raison pour laquelle je les écrirais de cette façon?

Leur lecture et les avantages / différences n'ont pas vraiment été expliqués.

AndrewC
la source
Un autre avantage énorme de la fonction en ligne est que vous pouvez sélectionner des colonnes ROWID (TIMESTAMP), tandis que vous ne pouvez pas insérer de données TIMESTAMP dans la table de retour dans la fonction multistatement!
Artru
3
Merci pour un excellent fil. J'ai beaucoup appris. Cependant, une chose à garder à l'esprit est lorsque vous modifiez une fonction qui était ITV en MSTV, le profileur pense que vous modifiez un ITV. Peu importe ce que vous faites pour obtenir la bonne syntaxe d'un point de vue MSTV, la recompilation échoue toujours, généralement autour de la première instruction après BEGIN. Le seul moyen de contourner cela était de supprimer l'ancienne fonction et de créer la nouvelle en tant que MSTV.
Fandango68

Réponses:

141

En recherchant le commentaire de Matt, j'ai révisé ma déclaration d'origine. Il a raison, il y aura une différence de performances entre une fonction de valeur de table en ligne (ITVF) et une fonction de valeur de table à instructions multiples (MSTVF) même si elles exécutent toutes deux simplement une instruction SELECT. SQL Server traitera un ITVF un peu comme unVIEWen ce qu 'il calculera un plan d'exécution en utilisant les dernières statistiques sur les tables en question. Un MSTVF équivaut à bourrer tout le contenu de votre instruction SELECT dans une variable de table, puis à s'y joindre. Par conséquent, le compilateur ne peut utiliser aucune statistique de table sur les tables du MSTVF. Donc, toutes choses étant égales par ailleurs (ce qu'elles sont rarement), l'ITVF fonctionnera mieux que le MSTVF. Dans mes tests, la différence de performance dans le temps de réalisation était négligeable mais d'un point de vue statistique, elle était notable.

Dans votre cas, les deux fonctions ne sont pas fonctionnellement équivalentes. La fonction MSTV effectue une requête supplémentaire chaque fois qu'elle est appelée et, plus important encore, filtre l'ID client. Dans une requête volumineuse, l'optimiseur ne pourrait pas tirer parti d'autres types de jointures car il aurait besoin d'appeler la fonction pour chaque customerId transmis. Cependant, si vous avez réécrit votre fonction MSTV comme ceci:

CREATE FUNCTION MyNS.GetLastShipped()
RETURNS @CustomerOrder TABLE
    (
    SaleOrderID    INT         NOT NULL,
    CustomerID      INT         NOT NULL,
    OrderDate       DATETIME    NOT NULL,
    OrderQty        INT         NOT NULL
    )
AS
BEGIN
    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a 
        INNER JOIN Sales.SalesOrderHeader b
            ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c 
            ON b.ProductID = c.ProductID
    WHERE a.OrderDate = (
                        Select Max(SH1.OrderDate)
                        FROM Sales.SalesOrderHeader As SH1
                        WHERE SH1.CustomerID = A.CustomerId
                        )
    RETURN
END
GO

Dans une requête, l'optimiseur serait en mesure d'appeler cette fonction une fois et de créer un meilleur plan d'exécution, mais ce ne serait toujours pas mieux qu'un ITVS équivalent ou non paramétré VIEW.

Les ITVF doivent être préférés aux MSTVF lorsque cela est possible car les types de données, la nullité et le classement des colonnes de la table tandis que vous déclarez ces propriétés dans une fonction de valeur de table à instructions multiples et, surtout, vous obtiendrez de meilleurs plans d'exécution de l'ITVF. D'après mon expérience, je n'ai pas trouvé de nombreuses circonstances où un ITVF était une meilleure option qu'un VIEW mais le kilométrage peut varier.

Merci à Matt.

Une addition

Depuis que j'ai vu cela se produire récemment, voici une excellente analyse effectuée par Wayne Sheffield comparant la différence de performance entre les fonctions Inline Table Valued et les fonctions multi-instructions.

Son article de blog original.

Copier sur SQL Server Central

Thomas
la source
40
Ce n'est tout simplement pas vrai - les fonctions multi-instructions sont très souvent un énorme impact sur les performances car elles empêchent l'optimiseur de requêtes d'utiliser des statistiques. Si j'avais 1 $ pour chaque fois que j'ai vu l'utilisation de fonctions multi-instructions provoquer un très mauvais choix de plan d'exécution (principalement parce qu'il estime généralement le nombre de lignes renvoyées comme 1), j'en aurais assez pour acheter une petite voiture.
Matt Whitfield
La meilleure explication que j'ai jamais trouvée se trouve dans la première réponse, et le message connexe: stackoverflow.com/questions/4109152/… Ne manquez pas le document connexe, vous pouvez le lire rapidement et c'est extrêmement intéressant.
JotaBe
1
Y aura-t-il une mise à jour de cette réponse pour SQL Server 2017?: Youtube.com/watch?time_continue=2&v=szTmo6rTUjM
Ralph
29

En interne, SQL Server traite une fonction de valeur de table en ligne un peu comme une vue et traite une fonction de valeur de table à instructions multiples de la même manière qu'une procédure stockée.

Lorsqu'une fonction table en ligne est utilisée dans le cadre d'une requête externe, le processeur de requêtes développe la définition UDF et génère un plan d'exécution qui accède aux objets sous-jacents, à l'aide des index sur ces objets.

Pour une fonction de valeur de table à instructions multiples, un plan d'exécution est créé pour la fonction elle-même et stocké dans le cache du plan d'exécution (une fois que la fonction a été exécutée la première fois). Si des fonctions de valeur de table à instructions multiples sont utilisées dans le cadre de requêtes plus importantes, l'optimiseur ne sait pas ce que la fonction retourne et fait donc certaines hypothèses standard - en fait, il suppose que la fonction renverra une seule ligne et que les retours de la fonction sera accessible en utilisant une analyse de table par rapport à une table avec une seule ligne.

Les fonctions de valeur de table à instructions multiples peuvent mal fonctionner lorsqu'elles renvoient un grand nombre de lignes et sont jointes dans les requêtes externes. Les problèmes de performances sont principalement dus au fait que l'optimiseur produira un plan en supposant qu'une seule ligne est retournée, ce qui ne sera pas nécessairement le plan le plus approprié.

En règle générale, nous avons constaté que, dans la mesure du possible, les fonctions de valeur de table en ligne devraient être utilisées de préférence à celles à instructions multiples (lorsque l'UDF sera utilisé dans le cadre d'une requête externe) en raison de ces problèmes de performances potentiels.

Paul McLoughlin
la source
2
Bien qu'elle puisse traiter des fonctions de valeur de table à plusieurs instructions similaires à une procédure stockée, une procédure stockée fonctionnellement identique est beaucoup plus rapide qu'une fonction de valeur de table pour les grands ensembles de données. Je m'en tiens aux proc stockés sur les fonctions de valeur de table multi-instructions.
Kekoa
6
Sauf si vous devez joindre ces résultats dans une autre requête.
Guillermo Gutiérrez
pourquoi ne pas utiliser les deux? Un proc stocké qui renvoie le résultat d'une fonction table multi-instructions. Le meilleur des deux mondes.
Robino
13

Il y a une autre différence. Une fonction de valeur de table en ligne peut être insérée, mise à jour et supprimée de - comme une vue. Des restrictions similaires s'appliquent - ne peuvent pas mettre à jour les fonctions à l'aide d'agrégats, ne peuvent pas mettre à jour les colonnes calculées, etc.

Craig Beere
la source
3

Vos exemples, je pense, répondent très bien à la question. La première fonction peut être effectuée en une seule sélection et est une bonne raison d'utiliser le style en ligne. La seconde pourrait probablement être faite comme une seule instruction (en utilisant une sous-requête pour obtenir la date maximale), mais certains codeurs peuvent trouver plus facile à lire ou plus naturel de le faire dans plusieurs instructions comme vous l'avez fait. Certaines fonctions ne peuvent tout simplement pas être exécutées en une seule instruction, et nécessitent donc la version multi-instructions.

Je suggère d'utiliser le plus simple (en ligne) dans la mesure du possible, et d'utiliser des instructions multiples lorsque cela est nécessaire (évidemment) ou lorsque la préférence / lisibilité personnelle rend la frappe supplémentaire.

Rayon
la source
Merci d'avoir répondu. Donc, fondamentalement, la multi-instruction ne doit vraiment être utilisée que lorsque la fonction est plus compliquée qu'il n'est possible de le faire dans une fonction en ligne, par souci de lisibilité? La multi-déclaration présente-t-elle des avantages en termes de performances?
AndrewC
Je ne sais pas, mais je ne pense pas. Il est probablement préférable de laisser le serveur sql déterminer les optimisations que vous pourriez essayer de faire manuellement (en utilisant des variables, des tables temporaires ou autre). Bien que vous puissiez certainement faire des tests de performances pour prouver / infirmer cela dans des cas spécifiques.
Ray
Merci encore. Je vais peut-être approfondir cela lorsque j'aurai plus de temps! :)
AndrewC
0

Je n'ai pas testé cela, mais une fonction multi-instructions met en cache l'ensemble de résultats. Il peut y avoir des cas où il se passe trop de choses pour que l’optimiseur intègre la fonction. Par exemple, supposons que vous ayez une fonction qui renvoie un résultat provenant de différentes bases de données en fonction de ce que vous passez en tant que «numéro d'entreprise». Normalement, vous pouvez créer une vue avec une union, puis filtrer par numéro d'entreprise, mais j'ai trouvé que parfois le serveur sql retire la totalité de l'union et n'est pas assez intelligent pour appeler la sélection. Une fonction de table peut avoir une logique pour choisir la source.

William Egge
la source
0

Un autre cas pour utiliser une fonction multi-ligne serait de contourner le serveur SQL de pousser vers le bas la clause where.

Par exemple, j'ai une table avec des noms de table et certains noms de table sont formatés comme C05_2019 et C12_2018 et toutes les tables formatées de cette façon ont le même schéma. Je voulais fusionner toutes ces données dans une table et analyser 05 et 12 dans une colonne CompNo et 2018,2019 dans une colonne année. Cependant, il existe d'autres tables comme ACA_StupidTable que je ne peux pas extraire CompNo et CompYr et obtiendrais une erreur de conversion si j'essayais. Donc, ma requête était en deux parties, une requête interne qui n'a renvoyé que des tables formatées comme 'C_______', puis la requête externe a fait une conversion de sous-chaîne et d'int. c'est-à-dire Cast (Substring (2, 2) as int) as CompNo. Tout semble bon, sauf que le serveur SQL a décidé de mettre ma fonction Cast avant le filtrage des résultats et j'ai donc une erreur de conversion de brouillage d'esprit. Une fonction de table d'instructions multiples peut empêcher cela de se produire,

William Egge
la source
0

Peut-être d'une manière très condensée. ITVF (inline TVF): plus si vous êtes une personne DB, c'est une sorte de vue paramétrée, prenez une seule SELECT

MTVF (Multi-statement TVF): développeur, crée et charge une variable de table.

LinchenPal
la source
-2

si vous allez faire une requête, vous pouvez joindre à votre fonction Inline Table Valued comme:

SELECT
    a.*,b.*
    FROM AAAA a
        INNER JOIN MyNS.GetUnshippedOrders() b ON a.z=b.z

cela entraînera peu de frais généraux et fonctionnera bien.

si vous essayez d'utiliser votre table d'instructions multiples évaluée dans une requête similaire, vous aurez des problèmes de performances:

SELECT
    x.a,x.b,x.c,(SELECT OrderQty FROM MyNS.GetLastShipped(x.CustomerID)) AS Qty
    FROM xxxx   x

parce que vous exécuterez la fonction 1 fois pour chaque ligne renvoyée, lorsque le jeu de résultats devient volumineux, il s'exécutera de plus en plus lentement.

KM.
la source
Ah, vous diriez donc que l'inline est bien meilleur en termes de performances?
AndrewC
1
Non, ils renvoient tous les deux une table, ce qui rend votre deuxième SQL invalide lorsque vous essayez de placer une table dans une colonne.
cjk
1
@ck, j'ai mis à jour la requête sur laquelle vous avez commenté. les paramètres de la fonction utilisée dans la deuxième fonction la prêtent à être utilisée comme sous-requête, ce qui entraînera de moins bonnes performances.
KM.