La explicación parece estar vinculada a una combinación de: a) un detalle del blog vinculado que no se mencionó en esta pregunta, b) la pragmática de los TVP que se ajustan a la forma en que siempre se han entrado y salido los parámetros, c) y la naturaleza de variables de tabla.
El detalle que falta en la publicación del blog vinculada es exactamente cómo las variables se pasan dentro y fuera de los procedimientos y funciones almacenados (que se relaciona con la redacción en la pregunta de "una versión más segura de paso por referencia si son parámetros de SALIDA") :
TSQL utiliza una semántica de copiar / copiar para pasar parámetros a procedimientos y funciones almacenados ...
... cuando el proceso almacenado termina de ejecutarse (sin encontrar un error) se realiza una copia que actualiza el parámetro pasado con cualquier cambio que se le haya realizado en el proceso almacenado.
El beneficio real de este enfoque está en el caso de error. Si se produce un error en medio de la ejecución de un procedimiento almacenado, los cambios realizados en los parámetros no se propagarán de nuevo al llamante.
Si la palabra clave OUTPUT no está presente, no se realiza ninguna copia.
El resultado final: los
parámetros de los procesos almacenados nunca reflejan la ejecución parcial del proceso almacenado si encuentra un error.
La parte 1 de este rompecabezas es que los parámetros siempre se pasan "por valor". Y, solo cuando el parámetro se marca como OUTPUT
y el Procedimiento almacenado se completa con éxito, el valor actual se devuelve. SiOUTPUT
valores se pasaron realmente "por referencia", entonces el puntero a la ubicación en memoria de esa variable sería lo que se pasó, no el valor en sí. Y si pasa el puntero (es decir, la dirección de memoria), los cambios realizados se reflejarán inmediatamente, incluso si la siguiente línea del Procedimiento almacenado causa un error y aborta la ejecución.
Para resumir la Parte 1: los valores variables siempre se copian; no están referenciados por su dirección de memoria.
Con la Parte 1 en mente, una política de copiar siempre valores de variables puede generar problemas de recursos cuando la variable que se pasa es bastante grande. No he probado para ver cómo se manejan los tipos de blob ( VARCHAR(MAX)
, NVARCHAR(MAX)
, VARBINARY(MAX)
, XML
, y las que no debe utilizarse más: TEXT
, NTEXT
, y IMAGE
), pero es seguro decir que cualquier tabla de datos que se aprobó en que podría ser bastante grande. Tendría sentido para aquellos que desarrollan la función TVP desear una verdadera capacidad de "pasar por referencia" para evitar que su nueva característica destruya un número saludable de sistemas (es decir, querer un enfoque más escalable). Como puede ver en la documentación, eso es lo que hicieron:
Transact-SQL pasa los parámetros con valores de tabla a las rutinas por referencia para evitar hacer una copia de los datos de entrada.
Además, este problema de administración de memoria no era un concepto nuevo, ya que se puede encontrar en la API SQLCLR que se introdujo en SQL Server 2005 (los TVP se introdujeron en SQL Server 2008). Al pasar NVARCHAR
y VARBINARY
datos en código SQLCLR (es decir, parámetros de entrada en los métodos de .NET dentro de un conjunto SQLCLR), tiene la opción de ir con el enfoque "de valor" mediante el uso de cualquiera de los dos SqlString
o SqlBinary
, respectivamente, o puede ir con el "por referencia "enfoque utilizando cualquiera SqlChars
o SqlBytes
respectivamente. Los tipos SqlChars
y SqlBytes
permiten la transmisión completa de los datos en .NET CLR de modo que pueda extraer pequeños fragmentos de valores grandes en lugar de copiar un valor completo de 200 MB (hasta 2 GB, a la derecha).
Para resumir la Parte 2: los TVP, por su propia naturaleza, tendrían una propensión a consumir mucha memoria (y, por lo tanto, deteriorarían el rendimiento) si permanecen dentro del modelo de "copiar siempre el valor". Por lo tanto, los TVP hacen un verdadero "pase por referencia".
La pieza final es por qué la Parte 2 es importante: por qué pasar un TVP realmente "por referencia" en lugar de hacer una copia de él cambiaría cualquier cosa. Y eso es respondido por el objetivo de diseño que es la base de la Parte 1: Los procedimientos almacenados que no se completan con éxito no deben alterar, de ninguna manera, ninguno de los parámetros de entrada, ya sea que estén marcados como OUTPUT
o no. Permitir operaciones DML tendría un efecto inmediato en el valor del TVP tal como existe en el contexto de la llamada (ya que pasar por referencia significa que está cambiando lo que se pasó, no una copia de lo que se pasó).
Ahora, alguien, en algún lugar, en este punto probablemente está hablando con su monitor y le dice: "Bueno, simplemente construya una instalación automática para revertir cualquier cambio realizado en los parámetros de TVP si alguno pasó al Procedimiento almacenado. Duh. Problema resuelto". No tan rapido. Aquí es donde entra en juego la naturaleza de las variables de tabla: ¡los cambios realizados en las variables de tabla no están vinculados por las transacciones! Entonces no hay forma de revertir los cambios. Y, de hecho, este es un truco que se usa para guardar la información generada dentro de una transacción si es necesario revertirla :-).
Para resumir la Parte 3: las variables de tabla no permiten realizar cambios de "deshacer" en el caso de un error que provoque el aborto del procedimiento almacenado. Y esto viola el objetivo de diseño de tener parámetros que nunca reflejen la ejecución parcial (Parte 1).
TYPE
variable de TVP de usuario o unaDECLARE x as TABLE (...)
) con un procedimiento almacenado? ¿Puedo hacerlo, aunque con una mayor huella de memoria, con una función en lugar deset @tvp = myfunction(@tvp)
si elRETURNS
valor de mi función es una tabla con el mismo DDL que el tipo de TVP?