Pourquoi les TVP doivent-ils être en lecture seule, et pourquoi les paramètres des autres types ne peuvent-ils pas être en lecture seule


19

Selon ce blog, les paramètres d'une fonction ou d'une procédure stockée sont essentiellement pass-by-value s'ils ne sont pas des OUTPUTparamètres et essentiellement traités comme une version plus sûre de pass-by-reference s'ils sont des OUTPUTparamètres.

Au début, je pensais que le but de forcer la déclaration de TVP READONLYétait de signaler clairement aux développeurs que le TVP ne pouvait pas être utilisé comme OUTPUTparamètre, mais il devait y en avoir plus car nous ne pouvons pas déclarer non-TVP comme READONLY. Par exemple, ce qui suit échoue:

create procedure [dbo].[test]
@a int readonly
as
    select @a

Msg 346, niveau 15, état 1, test de procédure
Le paramètre "@a" ne peut pas être déclaré LECTURE car il ne s'agit pas d'un paramètre table.

  1. Étant donné que les statistiques ne sont pas stockées sur TVP, quelle est la raison derrière la prévention des opérations DML?
  2. Est-ce lié à ne pas vouloir que le TVP soit un OUTPUTparamètre pour une raison quelconque?

Réponses:


19

L'explication semble être liée à une combinaison de: a) un détail du blog lié qui n'a pas été mentionné dans cette question, b) la pragmatique des TVP s'inscrivant dans la façon dont les paramètres ont toujours été entrés et sortis, c) et la nature des variables de table.

  1. Le détail manquant contenu dans le billet de blog lié est exactement la façon dont les variables sont transmises dans et hors des procédures et fonctions stockées (qui se rapportent à la formulation dans la question "d'une version plus sûre de la référence par contournement si ce sont des paramètres de SORTIE") :

    TSQL utilise une sémantique de copie / copie pour transmettre les paramètres aux procédures et fonctions stockées ....

    ... lorsque le processus stocké termine son exécution (sans générer d'erreur), une copie est effectuée qui met à jour le paramètre transmis avec toutes les modifications qui lui ont été apportées dans le processus stocké.

    Le véritable avantage de cette approche est dans le cas d'erreur. Si une erreur se produit au milieu de l'exécution d'une procédure stockée, les modifications apportées aux paramètres ne se propageront pas à l'appelant.

    Si le mot clé OUTPUT n'est pas présent, aucune copie n'est effectuée.


    Conclusion : les paramètres des proc stockés ne reflètent jamais l'exécution partielle du proc stocké en cas d'erreur.

    La partie 1 de ce puzzle est que les paramètres sont toujours passés "par valeur". Et, ce n'est que lorsque le paramètre est marqué comme OUTPUT et que la procédure stockée se termine avec succès que la valeur actuelle est effectivement renvoyée. Si les OUTPUTvaleurs étaient vraiment passées "par référence", alors le pointeur vers l'emplacement en mémoire de cette variable serait la chose qui a été passée, pas la valeur elle-même. Et si vous passez le pointeur (c'est-à-dire l'adresse mémoire), toutes les modifications apportées sont immédiatement reflétées, même si la ligne suivante de la procédure stockée provoque une erreur et abandonne l'exécution.

    Pour résumer la partie 1: les valeurs des variables sont toujours copiées; ils ne sont pas référencés par leur adresse mémoire.

  2. Avec la partie 1 à l'esprit, une politique de toujours copier les valeurs des variables peut entraîner des problèmes de ressources lorsque la variable transmise est assez grande. Je ne l' ai pas testé pour voir comment les types blob sont traités ( VARCHAR(MAX), NVARCHAR(MAX), VARBINARY(MAX), XMLet ceux qui ne doivent pas être utilisés plus: TEXT, NTEXTet IMAGE), mais il est sûr de dire que toutes les tables de données passées en pourrait être assez grand. Il serait logique pour ceux qui développent la fonctionnalité TVP de désirer une véritable capacité de "passage par référence" pour empêcher leur nouvelle fonctionnalité cool de détruire un nombre sain de systèmes (c'est-à-dire de vouloir une approche plus évolutive). Comme vous pouvez le voir dans la documentation, c'est ce qu'ils ont fait:

    Transact-SQL transmet les paramètres de table aux routines par référence pour éviter de faire une copie des données d'entrée.

    En outre, ce problème de gestion de la mémoire n'était pas un nouveau concept car il peut être trouvé dans l'API SQLCLR qui a été introduite dans SQL Server 2005 (les TVP ont été introduits dans SQL Server 2008). Lors du passage NVARCHARet des VARBINARYdonnées dans le code SQLCLR (c'est-à-dire les paramètres d'entrée sur les méthodes .NET dans un assemblage SQLCLR), vous avez la possibilité de suivre l'approche "par valeur" en utilisant soit SqlStringou SqlBinaryrespectivement, soit vous pouvez utiliser la "par référence" "approche en utilisant respectivement SqlCharsou SqlBytes. Les types SqlCharset SqlBytespermettent une diffusion complète des données dans le .NET CLR de sorte que vous pouvez extraire de petits morceaux de grandes valeurs au lieu de copier une valeur entière de 200 Mo (jusqu'à 2 Go, à droite).

    Pour résumer la partie 2: les TVP, de par leur nature même, auraient une propension à consommer beaucoup de mémoire (et donc à détériorer les performances) s'ils restent dans le modèle "toujours copier la valeur". Les TVP font donc un véritable "pass by reference".

  3. La dernière pièce est pourquoi la partie 2 est importante: pourquoi le fait de passer un TVP vraiment "par référence" au lieu d'en faire une copie changerait quoi que ce soit. Et cela est répondu par l'objectif de conception qui est à la base de la partie 1: les procédures stockées qui ne se terminent pas avec succès ne doivent en aucun cas modifier les paramètres d'entrée, qu'ils soient marqués OUTPUTou non. Autoriser les opérations DML aurait un effet immédiat sur la valeur du TVP tel qu'il existe dans le contexte d'appel (car le passage par référence signifie que vous changez la chose qui a été transmise, pas une copie de ce qui a été transmis).

    Maintenant, quelqu'un, quelque part, parle probablement à son moniteur à ce stade, "Eh bien, il suffit de construire une installation automagique pour annuler toutes les modifications apportées aux paramètres TVP si elles étaient transmises à la procédure stockée. Duh. Le problème est résolu." Pas si vite. C'est là que la nature des variables de tableau entre en jeu: les modifications apportées aux variables de tableau ne sont pas liées par les transactions! Il n'y a donc aucun moyen d'annuler les modifications. Et en fait, c'est une astuce utilisée pour enregistrer les informations générées dans une transaction s'il doit y avoir un retour en arrière :-).

    Pour résumer la partie 3: les variables de table ne permettent pas d'annuler les modifications qui leur sont apportées en cas d'erreur provoquant l'abandon de la procédure stockée. Et cela viole l'objectif de conception d'avoir des paramètres ne reflétant jamais l'exécution partielle (Partie 1).

Ergo: le READONLYmot-clé est nécessaire pour empêcher les opérations DML sur les TVP car ce sont des variables de table qui sont réellement passées "par référence", et donc toute modification de celles-ci serait immédiatement reflétée, même si la procédure stockée rencontre une erreur, et il n'y a pas autre moyen d'empêcher cela.

De plus, les paramètres d'autres types de données ne peuvent pas être utilisés READONLYcar ils sont déjà des copies de ce qui a été transmis, et ne protègent donc rien qui ne soit déjà protégé. Cela, et la façon dont les paramètres des autres types de données fonctionnent étaient destinés à être en lecture-écriture, il serait donc probablement encore plus difficile de modifier cette API pour inclure maintenant un concept en lecture seule.


Explication très détaillée. Merci. Il n'y a donc aucun moyen de modifier une variable de table passée (soit une TYPEvariable TVP utilisateur ou a DECLARE x as TABLE (...)) avec une procédure stockée? Puis-je le faire, bien qu'avec une plus grande empreinte mémoire, avec une fonction à la place avec set @tvp = myfunction(@tvp)si la RETURNSvaleur de ma fonction est une table avec le même DDL que le type TVP?
mpag

@mpag Merci. Un TVP est une variable de table, il n'y a pas de différence. Vous ne transmettez pas le type, vous transmettez une variable de table créée à partir d'un type ou d'une déclaration de schéma explicite. En outre, vous ne pouvez pas SETune variable de table, du moins pas que je sache. Et même si vous pouviez: a) vous ne pouvez pas accéder à un jeu de résultats via l' =opérateur, et b) le TVP est toujours marqué comme READONLY, donc le définir violerait cela. Vider simplement le contenu dans une table temporaire ou une autre variable de table que vous créez dans le proc.
Solomon Rutzky

Merci encore. J'ai décidé d'utiliser essentiellement une approche de table temporaire.
mpag

5

Réponse Wiki communautaire générée à partir d'un commentaire sur la question par Martin Smith

Il existe un élément Connect actif (soumis par Erland Sommarskog) pour cela:

Relâchez la restriction selon laquelle les paramètres de table doivent être en lecture seule lorsque les SP s'appellent les uns les autres

La seule réponse de Microsoft à ce jour dit (c'est moi qui souligne):

Merci pour les commentaires à ce sujet. Nous avons reçu des commentaires similaires d'un grand nombre de clients. Autoriser la lecture / écriture de paramètres de valeur de table implique un travail considérable du côté du moteur SQL ainsi que des protocoles clients. En raison de contraintes de temps / ressources ainsi que d'autres priorités, nous ne pourrons pas reprendre ce travail dans le cadre de la version SQL Server 2008. Cependant, nous avons étudié ce problème et nous l'avons fermement dans notre radar pour le résoudre dans le cadre de la prochaine version de SQL Server. Nous apprécions et apprécions les commentaires ici.

Srini Acharya
Senior Program Manager
SQL Server Relational Engine

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.