¿Por qué los TVP deben ser READONLY y por qué los parámetros de otros tipos no pueden ser READONLY?

19

De acuerdo con este blog, los parámetros de una función o un procedimiento almacenado son esencialmente paso por valor si no son OUTPUTparámetros, y esencialmente se tratan como una versión más segura de paso por referencia si son OUTPUTparámetros.

Al principio pensé que el objetivo de obligar a TVP a declararse READONLYera indicar claramente a los desarrolladores que TVP no se puede usar como OUTPUTparámetro, pero debe haber más actividad porque no podemos declarar que no es TVP READONLY. Por ejemplo, lo siguiente falla:

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

Mensaje 346, Nivel 15, Estado 1, Prueba de procedimiento
El parámetro "@a" no puede declararse READONLY ya que no es un parámetro con valores de tabla.

  1. Dado que las estadísticas no se almacenan en TVP, ¿cuál es la razón detrás de la prevención de las operaciones DML?
  2. ¿Está relacionado con no querer que TVP sea un OUTPUTparámetro por alguna razón?
Erik
fuente

Respuestas:

19

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.

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

  2. 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 NVARCHARy VARBINARYdatos 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 SqlStringo SqlBinary, respectivamente, o puede ir con el "por referencia "enfoque utilizando cualquiera SqlCharso SqlBytesrespectivamente. Los tipos SqlCharsy SqlBytespermiten 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".

  3. 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 OUTPUTo 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).

Ergo: la READONLYpalabra clave es necesaria para evitar operaciones DML en TVP, ya que son variables de tabla que realmente se pasan "por referencia" y, por lo tanto, cualquier modificación a ellas se reflejaría de inmediato, incluso si el procedimiento almacenado encuentra un error y no hay Otra forma de evitar eso.

Además, los parámetros de otros tipos de datos no pueden usarse READONLYporque ya son copias de lo que se pasó y, por lo tanto, no protegerían nada que ya no esté protegido. Eso, y la forma en que funcionan los parámetros de los otros tipos de datos tenía la intención de ser de lectura-escritura, por lo que probablemente sería aún más trabajo alterar esa API para incluir ahora un concepto de solo lectura.

Solomon Rutzky
fuente
Explicación muy detallada. Gracias. Entonces, ¿no hay forma de modificar una variable de tabla aprobada (ya sea una TYPEvariable de TVP de usuario o una DECLARE x as TABLE (...)) con un procedimiento almacenado? ¿Puedo hacerlo, aunque con una mayor huella de memoria, con una función en lugar de set @tvp = myfunction(@tvp)si el RETURNSvalor de mi función es una tabla con el mismo DDL que el tipo de TVP?
mpag
@mpag Gracias. Un TVP es una variable de tabla, no hay diferencia. No pasa el tipo, pasa una variable de tabla creada a partir de un tipo o de una declaración de esquema explícito. Además, no puede SETuna variable de tabla, al menos no que yo sepa. E incluso si pudiera: a) no puede acceder a un conjunto de resultados a través del =operador, yb) el TVP todavía está marcado como READONLY, por lo que establecerlo violaría eso. Simplemente descargue el contenido en una tabla temporal u otra variable de tabla que cree dentro del proceso.
Solomon Rutzky
Gracias de nuevo. En esencia, he decidido utilizar un enfoque de tabla temporal.
mpag
5

Respuesta de Community Wiki generada a partir de un comentario sobre la pregunta de Martin Smith

Hay un elemento de Connect activo (enviado por Erland Sommarskog) para esto:

Restrinja la restricción de que los parámetros de la tabla deben ser de solo lectura cuando los SP se llaman entre sí

La única respuesta de Microsoft hasta ahora dice (énfasis agregado):

Gracias por los comentarios sobre esto. Hemos recibido comentarios similares de una gran cantidad de clientes. Permitir que los parámetros con valores de tabla se lean / escriban implica bastante trabajo en el lado del motor SQL, así como en los protocolos del cliente. Debido a limitaciones de tiempo / recursos, así como a otras prioridades, no podremos realizar este trabajo como parte de la versión de SQL Server 2008. Sin embargo, hemos investigado este problema y tenemos esto firmemente en nuestro radar para abordarlo como parte de la próxima versión de SQL Server. Agradecemos y agradecemos los comentarios aquí.

Srini Acharya
Gerente senior de programas
Motor relacional de SQL Server

Paul White
fuente