Tamaño máximo de una variable varchar (max)

89

En cualquier momento en el pasado, si alguien me hubiera preguntado el tamaño máximo de a varchar(max), habría dicho 2GB, o habría buscado una cifra más exacta (2 ^ 31-1, o 2147483647).

Sin embargo, en algunas pruebas recientes, descubrí que las varchar(max)variables aparentemente pueden exceder este tamaño:

create table T (
    Val1 varchar(max) not null
)
go
declare @KMsg varchar(max) = REPLICATE('a',1024);
declare @MMsg varchar(max) = REPLICATE(@KMsg,1024);
declare @GMsg varchar(max) = REPLICATE(@MMsg,1024);
declare @GGMMsg varchar(max) = @GMsg + @GMsg + @MMsg;
select LEN(@GGMMsg)
insert into T(Val1) select @GGMMsg
select LEN(Val1) from T

Resultados:

(no column name)
2148532224
(1 row(s) affected)
Msg 7119, Level 16, State 1, Line 6
Attempting to grow LOB beyond maximum allowed size of 2147483647 bytes.
The statement has been terminated.

(no column name)
(0 row(s) affected)

Entonces, dado que ahora sé que una variable puede superar la barrera de los 2GB, ¿alguien sabe cuál es el límite real para una varchar(max)variable?


(La prueba anterior se completó en SQL Server 2008 (no R2). Me interesaría saber si se aplica a otras versiones)

Damien_The_Unbeliever
fuente
declare @x varchar(max) = 'XX'; SELECT LEN(REPLICATE(@x,2147483647))da 4294967294para mí, pero tarda mucho en ejecutarse, incluso después de que SELECTha regresado, por lo que no estoy seguro de qué dedica ese tiempo extra.
Martin Smith

Respuestas:

74

Por lo que puedo decir, no hay límite superior en 2008.

En SQL Server 2005, el código de su pregunta falla en la asignación a la @GGMMsgvariable con

Intentando hacer crecer LOB más allá del tamaño máximo permitido de 2,147,483,647 bytes.

el siguiente código falla con

REPLICAR: la longitud del resultado supera el límite de longitud (2 GB) del tipo grande de destino.

Sin embargo, parece que estas limitaciones se han eliminado silenciosamente. En 2008

DECLARE @y VARCHAR(MAX) = REPLICATE(CAST('X' AS VARCHAR(MAX)),92681); 

SET @y = REPLICATE(@y,92681);

SELECT LEN(@y) 

Devoluciones

8589767761

Ejecuté esto en mi máquina de escritorio de 32 bits, por lo que esta cadena de 8 GB excede la memoria direccionable

Corriendo

select internal_objects_alloc_page_count
from sys.dm_db_task_space_usage
WHERE session_id = @@spid

Devuelto

internal_objects_alloc_page_co 
------------------------------ 
2144456    

así que supongo que todo esto simplemente se almacena en LOBpáginas tempdbsin validación de longitud. El crecimiento del recuento de páginas estuvo asociado con la SET @y = REPLICATE(@y,92681);declaración. La asignación de variable inicial ay @yel LENcálculo no aumentó esto.

La razón para mencionar esto es porque el recuento de páginas es mucho más de lo que esperaba. Suponiendo una página de 8 KB, esto da como resultado 16,36 GB, que obviamente es más o menos el doble de lo que parecería ser necesario. Especulo que esto se debe probablemente a la ineficacia de la operación de concatenación de cadenas que necesita copiar toda la cadena enorme y agregar un fragmento al final en lugar de poder agregar al final de la cadena existente. Desafortunadamente, por el momento, el .WRITEmétodo no es compatible con variables varchar (max).

Adición

También probé el comportamiento con concatenación nvarchar(max) + nvarchar(max)y nvarchar(max) + varchar(max). Ambos permiten superar el límite de 2 GB. Intentar almacenar los resultados de esto en una tabla y luego falla, sin embargo, con el mensaje de error Attempting to grow LOB beyond maximum allowed size of 2147483647 bytes.nuevamente. El script para eso se encuentra a continuación (puede tardar mucho en ejecutarse).

DECLARE @y1 VARCHAR(MAX) = REPLICATE(CAST('X' AS VARCHAR(MAX)),2147483647); 
SET @y1 = @y1 + @y1;
SELECT LEN(@y1), DATALENGTH(@y1)  /*4294967294, 4294967292*/


DECLARE @y2 NVARCHAR(MAX) = REPLICATE(CAST('X' AS NVARCHAR(MAX)),1073741823); 
SET @y2 = @y2 + @y2;
SELECT LEN(@y2), DATALENGTH(@y2)  /*2147483646, 4294967292*/


DECLARE @y3 NVARCHAR(MAX) = @y2 + @y1
SELECT LEN(@y3), DATALENGTH(@y3)   /*6442450940, 12884901880*/

/*This attempt fails*/
SELECT @y1 y1, @y2 y2, @y3 y3
INTO Test
Martin Smith
fuente
1
Excelente, por lo que parece que la documentación es bastante "incompleta", observo que la página habitual se refiere a un "tamaño de almacenamiento" máximo, que presumiblemente solo se aplica a columnas, no a variables.
Damien_The_Unbeliever
@Damien - Definitivamente parece así. No estoy seguro de si hay algún otro límite que se pueda alcanzar en términos de número total de páginas, pero creo que esto se almacena en una estructura de árbol B (basada en la p. 381 de las partes internas de SQL Server 2008), por lo que en principio podría extenderse definitivamente.
Martin Smith
@Damien_The_Unbeliever - La documentación aquí parece totalmente incorrecta basada en los experimentos aquí, indicando de manera bastante inequívoca que "las variables y parámetros de tipo de datos de objetos grandes (LOB) ... los tipos pueden tener un tamaño de hasta 2 GB"
Martin Smith
Un poco inútil pero interesante. En teoría, podría llenar el disco con una variable ... :-)
gbn
Tengo algunas dudas aquí porque almacenar un valor en una variable no es lo mismo que almacenarlo en una columna. ¿Te apetece probar esto con una columna en su lugar, o tienes una actualización? Incluso SQL Server 2000 podría tener varcharvalores de más de 8000 caracteres en cadenas literales en el código, siempre que no intente ponerlo en una variable o varcharcolumna.
ErikE
9

EDITAR : Después de una mayor investigación, mi suposición original de que se trataba de una anomalía (¿error?) De la declare @var datatype = valuesintaxis es incorrecta.

Modifiqué su script para 2005 ya que esa sintaxis no es compatible, luego probé la versión modificada en 2008. En 2005, aparece el Attempting to grow LOB beyond maximum allowed size of 2147483647 bytes.mensaje de error. En 2008, la secuencia de comandos modificada todavía tiene éxito.

declare @KMsg varchar(max); set @KMsg = REPLICATE('a',1024);
declare @MMsg varchar(max); set @MMsg = REPLICATE(@KMsg,1024);
declare @GMsg varchar(max); set @GMsg = REPLICATE(@MMsg,1024);
declare @GGMMsg varchar(max); set @GGMMsg = @GMsg + @GMsg + @MMsg;
select LEN(@GGMMsg)
Joe Stefanelli
fuente
El script siempre produce un error (al intentar hacer la inserción de la tabla), pero en 2008, siempre obtengo un resultado en el primer conjunto de resultados, lo que indica que la variable existe y tiene más de 2 ^ 31-1 de longitud.
Damien_The_Unbeliever
@Damien_The_Unbeliever: reduje el script a la parte variable y ahora obtengo los mismos resultados que tú. En 2005, aparece el Attempting to grow...error en la set @GGMMsg=...declaración. En 2008, el guión tiene éxito.
Joe Stefanelli