Tamanho máximo de uma variável varchar (max)

88

A qualquer momento no passado, se alguém tivesse me perguntado o tamanho máximo para um varchar(max), eu teria dito que 2 GB, ou olhou para uma forma mais exata figura (2 ^ 31-1, ou 2147483647).

No entanto, em alguns testes recentes, descobri que as varchar(max)variáveis ​​podem aparentemente exceder este tamanho:

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)

Portanto, como agora sei que uma variável pode exceder a barreira de 2 GB - alguém sabe qual é o limite real de uma varchar(max)variável?


(Teste acima concluído no SQL Server 2008 (não R2). Gostaria de saber se ele se aplica a outras versões)

Damien_The_Unbeliever
fonte
declare @x varchar(max) = 'XX'; SELECT LEN(REPLICATE(@x,2147483647))4294967294para mim, mas leva muito tempo para ser executado - mesmo depois que o SELECTvoltou, não tenho certeza do que esse tempo extra é gasto fazendo.
Martin Smith

Respostas:

73

Tanto quanto posso dizer, não há limite máximo em 2008.

No SQL Server 2005, o código em sua pergunta falha na atribuição à @GGMMsgvariável com

Tentativa de aumentar o LOB além do tamanho máximo permitido de 2.147.483.647 bytes.

o código abaixo falha com

REPLICATE: O comprimento do resultado excede o limite de comprimento (2 GB) do tipo grande de destino.

No entanto, parece que essas limitações foram discretamente levantadas. Em 2008

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

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

SELECT LEN(@y) 

Devoluções

8589767761

Eu executei isso na minha máquina de desktop de 32 bits, então essa string de 8 GB é muito mais do que memória endereçável

Corrida

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

Retornou

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

então presumo que tudo isso seja armazenado em LOBpáginas tempdbsem validação de comprimento. O crescimento da contagem de páginas estava associado à SET @y = REPLICATE(@y,92681);declaração. A atribuição inicial da variável @ye o LENcálculo não aumentaram isso.

A razão para mencionar isso é porque a contagem de páginas é muito maior do que eu esperava. Supondo uma página de 8 KB, isso resulta em 16,36 GB, o que é obviamente mais ou menos o dobro do que seria necessário. Eu especulo que isso provavelmente se deve à ineficiência da operação de concatenação de string, que precisa copiar toda a string enorme e anexar um pedaço ao final, em vez de ser capaz de adicionar ao final da string existente. Infelizmente, no momento, o .WRITEmétodo não é compatível com variáveis ​​varchar (max).

Adição

Também testei o comportamento com concatenação nvarchar(max) + nvarchar(max)e nvarchar(max) + varchar(max). Ambos permitem que o limite de 2 GB seja excedido. A tentativa de armazenar os resultados disso em uma tabela falha, porém com a mensagem de erro Attempting to grow LOB beyond maximum allowed size of 2147483647 bytes.novamente. O script para isso está abaixo (pode levar muito tempo para ser executado).

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
fonte
1
Excelente - então parece que a documentação está um tanto "incompleta" - eu noto que a página usual se refere a um "tamanho de armazenamento" máximo, que presumivelmente se aplica apenas a colunas, não variáveis.
Damien_The_Unbeliever
@Damien - Definitivamente parece que sim. Não tenho certeza se há algum outro limite que pode ser alcançado em termos de número total de páginas, mas acho que isso está armazenado em uma estrutura de árvore B (com base na p.381 de internos do SQL Server 2008), portanto, em princípio, pode ser estendido definitivamente.
Martin Smith
@Damien_The_Unbeliever - A documentação aqui parece totalmente errada com base nos experimentos aqui, afirmando de forma bastante inequívoca que "Variáveis ​​e parâmetros de tipo de dados de objeto grande (LOB) ... os tipos podem ter até 2 GB de tamanho"
Martin Smith
Um pouco inútil, mas interessante. Em teoria, você poderia preencher o disco com uma variável ... :-)
gbn
Tenho alguma hesitação aqui porque armazenar um valor em uma variável não é o mesmo que armazená-lo em uma coluna. Você gostaria de tentar fazer isso com uma coluna - ou você tem uma atualização? Até o SQL Server 2000 poderia ter varcharvalores maiores que 8.000 caracteres em cadeias literais no código, contanto que você não tentasse colocá-lo em uma variável ou varcharcoluna.
ErikE
9

EDIT : Após uma investigação mais aprofundada, minha suposição original de que esta era uma anomalia (bug?) Da declare @var datatype = valuesintaxe está incorreta.

Modifiquei seu script para 2005, pois essa sintaxe não é compatível e tentei a versão modificada em 2008. Em 2005, recebo a Attempting to grow LOB beyond maximum allowed size of 2147483647 bytes.mensagem de erro. Em 2008, o script modificado ainda é bem-sucedido.

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
fonte
O script sempre produz um erro (ao tentar fazer a inserção da tabela), mas em 2008, sempre obtenho um resultado no primeiro conjunto de resultados, indicando que a variável existe e tem mais de 2 ^ 31-1 de comprimento.
Damien_The_Unbeliever
@Damien_The_Unbeliever: Eu reduzi o script apenas para a parte variável e agora obtenho os mesmos resultados que você. Em 2005, recebo o Attempting to grow...erro na set @GGMMsg=...declaração. Em 2008, o roteiro é um sucesso.
Joe Stefanelli