Perché i TVP devono essere READONLY e perché i parametri di altri tipi non possono essere READONLY


19

Secondo questo blog, i parametri di una funzione o di una procedura memorizzata sono essenzialmente pass-by-value se non sono OUTPUTparametri e essenzialmente trattati come una versione più sicura di pass-by-reference se sono OUTPUTparametri.

All'inizio ho pensato che l'obiettivo di forzare la dichiarazione di TVP READONLYfosse quello di segnalare chiaramente agli sviluppatori che il TVP non può essere usato come OUTPUTparametro, ma ci devono essere molti altri perché non possiamo dichiarare non-TVP come READONLY. Ad esempio, quanto segue non riesce:

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

Messaggio 346, livello 15, stato 1, test di procedura
Il parametro "@a" non può essere dichiarato READONLY poiché non è un parametro con valori di tabella.

  1. Poiché le statistiche non sono archiviate su TVP, qual è la logica alla base della prevenzione delle operazioni DML?
  2. È collegato al fatto che non si desidera che TVP sia un OUTPUTparametro per qualche motivo?

Risposte:


19

La spiegazione sembra essere legata a una combinazione di: a) un dettaglio del blog collegato che non è stato menzionato in questa domanda, b) la pragmatica dei TVP che si adatta a come i parametri sono sempre stati passati e usciti, c) e la natura delle variabili di tabella.

  1. Il dettaglio mancante contenuto nel post di blog collegato è esattamente il modo in cui le variabili vengono passate dentro e fuori dalle Stored procedure e funzioni (che si riferisce alla frase nella domanda di "una versione più sicura del pass-by-reference se sono parametri OUTPUT") :

    TSQL utilizza una semantica copia-in / copia-fuori per passare parametri a procedure e funzioni memorizzate ....

    ... quando l'esecuzione del proc memorizzato termina (senza colpire un errore) viene eseguita una copia che aggiorna il parametro passato con qualsiasi modifica apportata ad esso nel proc memorizzato.

    Il vero vantaggio di questo approccio è nel caso di errore. Se si verifica un errore nel mezzo dell'esecuzione di una procedura memorizzata, eventuali modifiche apportate ai parametri non verranno propagate al chiamante.

    Se la parola chiave OUTPUT non è presente, non viene eseguita alcuna copia.

    La linea di fondo: i
    parametri per i proc memorizzati non riflettono mai l'esecuzione parziale del proc memorizzato se si è verificato un errore.

    La prima parte di questo puzzle è che i parametri vengono sempre passati "per valore". E 'solo quando il parametro è contrassegnato come OUTPUT e la Stored Procedure si completa correttamente che il valore corrente viene effettivamente restituito. Se i OUTPUTvalori fossero veramente passati "per riferimento", allora il puntatore alla posizione in memoria di quella variabile sarebbe la cosa che è stata passata, non il valore stesso. E se si passa il puntatore (ovvero l'indirizzo di memoria), le eventuali modifiche apportate vengono immediatamente riflesse, anche se la riga successiva della Stored Procedure causa un errore e interrompe l'esecuzione.

    Per riassumere la Parte 1: i valori delle variabili vengono sempre copiati; non fanno riferimento al loro indirizzo di memoria.

  2. Tenendo presente la Parte 1, una politica di copia sempre dei valori delle variabili può portare a problemi di risorse quando la variabile che viene trasferita è piuttosto grande. Non ho ancora testato per vedere come i tipi BLOB vengono gestiti ( VARCHAR(MAX), NVARCHAR(MAX), VARBINARY(MAX), XML, e quelli che non deve essere più usato: TEXT, NTEXT, e IMAGE), ma è sicuro di dire che qualsiasi tabella di dati che viene passata in potrebbe essere abbastanza grande. Sarebbe sensato per coloro che sviluppano la funzionalità TVP desiderare una vera capacità "pass-by-reference" per impedire alla loro nuova e interessante funzione di distruggere un numero salutare di sistemi (cioè desiderare un approccio più scalabile). Come puoi vedere nella documentazione, è quello che hanno fatto:

    Transact-SQL passa i parametri con valori di tabella alle routine per riferimento per evitare di creare una copia dei dati di input.

    Inoltre, questo problema di gestione della memoria non era un nuovo concetto poiché si trova nell'API SQLCLR introdotta in SQL Server 2005 (i TVP sono stati introdotti in SQL Server 2008). Durante il passaggio NVARCHARe i VARBINARYdati nel codice SQLCLR (ovvero i parametri di input sui metodi .NET all'interno di un assembly SQLCLR), si ha la possibilità di seguire l'approccio "per valore" utilizzando uno SqlStringo SqlBinaryrispettivamente, oppure si può andare con "per riferimento "approccio utilizzando uno SqlCharso SqlBytesrispettivamente. I tipi SqlCharse SqlBytesconsentono lo streaming completo dei dati nel CLR .NET in modo da poter estrarre piccoli blocchi di valori di grandi dimensioni anziché copiare un intero valore di 200 MB (fino a 2 GB, a destra).

    Riassumendo la Parte 2: i TVP, per loro stessa natura, avrebbero una propensione a consumare molta memoria (e quindi a peggiorare le prestazioni) se rimanessero nel modello "copia sempre il valore". Quindi i TVP eseguono un vero "passaggio per riferimento".

  3. Il pezzo finale è il motivo per cui la Parte 2 è importante: perché passare in un TVP veramente "per riferimento" invece di farne una copia cambierebbe qualcosa. E ciò risponde all'obiettivo di progettazione che è alla base della Parte 1: le Stored procedure che non vengono completate correttamente non devono alterare, in alcun modo, nessuno dei parametri di input, indipendentemente dal fatto che siano contrassegnati OUTPUTo meno. Consentire le operazioni DML avrebbe un effetto immediato sul valore del TVP come esiste nel contesto di chiamata (poiché passare per riferimento significa che stai cambiando la cosa che è stata passata, non una copia di ciò che è stato passato).

    Ora, qualcuno, da qualche parte, sta probabilmente parlando al proprio monitor dicendo: "Bene, basta costruire in una struttura automagica per ripristinare qualsiasi modifica apportata ai parametri TVP se ne fossero passati nella Stored Procedure. Duh. Problema risolto". Non così in fretta. È qui che entra in gioco la natura delle variabili di tabella: le modifiche apportate alle variabili di tabella non sono vincolate dalle Transazioni! Quindi non c'è modo di ripristinare le modifiche. E in effetti, questo è un trucco usato per salvare le informazioni generate all'interno di una transazione se deve esserci un rollback :-).

    Per riassumere, parte 3: le variabili di tabella non consentono di "annullare" le modifiche apportate ad esse nel caso di un errore che causa l'interruzione della procedura memorizzata. E questo viola l'obiettivo di progettazione di avere parametri che non riflettano mai l'esecuzione parziale (Parte 1).

Ergo: la READONLYparola chiave è necessaria per impedire le operazioni DML sui TVP poiché sono variabili di tabella che vengono effettivamente passate "per riferimento", e quindi qualsiasi modifica ad esse verrebbe immediatamente riflessa, anche se la Stored Procedure riscontra un errore, e non c'è altro modo per impedirlo.

Inoltre, i parametri di altri tipi di dati non possono essere utilizzati READONLYperché sono già copie di ciò che è stato passato e quindi non proteggerebbero tutto ciò che non è già protetto. Questo e il modo in cui funzionano i parametri degli altri tipi di dati erano destinati a essere di lettura-scrittura, quindi probabilmente sarebbe ancora più lavoro modificare quell'API per includere ora un concetto di sola lettura.


Spiegazione molto dettagliata. Grazie. Quindi non c'è modo di modificare una variabile della tabella passata (una TYPEvariabile TVP utente o a DECLARE x as TABLE (...)) con una procedura memorizzata? Posso farlo, anche se con un footprint di memoria maggiore, con una funzione anziché set @tvp = myfunction(@tvp)se il RETURNSvalore della mia funzione è una tabella con lo stesso DDL del tipo TVP?
mpag,

@mpag Grazie. Un TVP è una variabile di tabella, non c'è differenza. Non si passa il tipo, si passa in una variabile di tabella creata da un tipo o da una dichiarazione di schema esplicita. Inoltre, non è possibile SETuna variabile di tabella, almeno non di cui io sia a conoscenza. E anche se tu potessi: a) non puoi accedere a un set di risultati tramite l' =operatore eb) il TVP è ancora contrassegnato come READONLY, quindi impostarlo violerebbe quello. Basta scaricare il contenuto in una tabella temporanea o in un'altra variabile di tabella creata all'interno del proc.
Solomon Rutzky,

Grazie ancora. Ho deciso di utilizzare essenzialmente un approccio di tabella temporanea.
mpag,

5

Community Wiki risposta generata da un commento sulla domanda di Martin Smith

C'è un elemento Connect attivo (inviato da Erland Sommarskog) per questo:

Rilassa la limitazione che i parametri della tabella devono essere di sola lettura quando gli SP si chiamano l'un l'altro

L'unica risposta di Microsoft finora dice (enfasi aggiunta):

Grazie per il feedback su questo. Abbiamo ricevuto feedback simili da un gran numero di clienti. Consentire la lettura / scrittura di parametri con valori di tabella implica un bel po 'di lavoro sul lato motore SQL e sui protocolli client. A causa di vincoli di tempo / risorse e di altre priorità, non saremo in grado di intraprendere questo lavoro come parte della versione di SQL Server 2008. Tuttavia, abbiamo esaminato questo problema e lo abbiamo saldamente inserito nel nostro radar per risolverlo come parte della prossima versione di SQL Server. Apprezziamo e accogliamo con favore il feedback qui.

Motore relazionale Srini Acharya
Senior Program Manager
SQL Server

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.