Taille maximale d'une variable varchar (max)

88

À tout moment dans le passé, si quelqu'un m'avait demandé la taille maximale d'un varchar(max), j'aurais dit 2 Go, ou recherché un chiffre plus exact (2 ^ 31-1 ou 2147483647).

Cependant, dans certains tests récents, j'ai découvert que les varchar(max)variables peuvent apparemment dépasser cette taille:

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

Résultats:

(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)

Donc, étant donné que je sais maintenant qu'une variable peut dépasser la barrière de 2 Go, est-ce que quelqu'un sait quelle est la limite réelle pour une varchar(max)variable?


(Test ci-dessus terminé sur SQL Server 2008 (pas R2). Je serais intéressé de savoir s'il s'applique à d'autres versions)

Damien_The_Unbeliever
la source
declare @x varchar(max) = 'XX'; SELECT LEN(REPLICATE(@x,2147483647))donne 4294967294pour moi mais prend beaucoup de temps à courir - même après le SELECTretour, donc je ne sais pas à quoi sert ce temps supplémentaire.
Martin Smith

Réponses:

73

Pour autant que je sache, il n'y a pas de limite supérieure en 2008.

Dans SQL Server 2005, le code de votre question échoue lors de l'affectation de la @GGMMsgvariable avec

Tentative de croissance du LOB au-delà de la taille maximale autorisée de 2 147 483 647 octets.

le code ci-dessous échoue avec

RÉPLIQUER: La longueur du résultat dépasse la limite de longueur (2 Go) du type de grande taille cible.

Cependant, il semble que ces limitations aient été tranquillement levées. En 2008

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

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

SELECT LEN(@y) 

Retour

8589767761

J'ai exécuté ceci sur ma machine de bureau 32 bits, donc cette chaîne de 8 Go dépasse largement la mémoire adressable

Fonctionnement

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

Revenu

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

donc je suppose que tout cela est simplement stocké dans les LOBpages tempdbsans validation de la longueur. La croissance du nombre de pages était entièrement associée à l' SET @y = REPLICATE(@y,92681);instruction. L'affectation initiale des variables à @yet le LENcalcul ne l'ont pas augmenté.

La raison de mentionner cela est que le nombre de pages est largement supérieur à ce à quoi je m'attendais. En supposant une page de 8 Ko, cela équivaut à 16,36 Go, ce qui est évidemment plus ou moins le double de ce qui semble nécessaire. Je suppose que cela est probablement dû à l'inefficacité de l'opération de concaténation de chaînes qui nécessite de copier la totalité de la chaîne énorme et d'ajouter un morceau à la fin plutôt que de pouvoir ajouter à la fin de la chaîne existante. Malheureusement, pour le moment, la .WRITEméthode n'est pas prise en charge pour les variables varchar (max).

Une addition

J'ai également testé le comportement avec la concaténation nvarchar(max) + nvarchar(max)et nvarchar(max) + varchar(max). Les deux permettent de dépasser la limite de 2 Go. Essayer de stocker ensuite les résultats de ceci dans une table échoue alors à Attempting to grow LOB beyond maximum allowed size of 2147483647 bytes.nouveau avec le message d'erreur . Le script pour cela est ci-dessous (peut prendre du temps à s'exécuter).

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
la source
1
Excellent - il semblerait donc que la documentation soit plutôt "incomplète" - Je note que la page habituelle fait référence à une "taille de stockage" maximale, qui ne s'applique vraisemblablement qu'aux colonnes et non aux variables.
Damien_The_Unbeliever
@Damien - Apparaît définitivement de cette façon. Je ne sais pas s'il existe une autre limite pouvant être atteinte en termes de nombre total de pages, mais je pense que cela est stocké dans une structure arborescente B (basée sur la p.381 des internes de SQL Server 2008) donc en principe pourrait être étendue définitivement.
Martin Smith
@Damien_The_Unbeliever - La documentation ici semble tout à fait erronée sur la base des expériences ici, indiquant assez sans ambiguïté que "les variables et paramètres de type de données de grand objet (LOB) ... les types peuvent atteindre jusqu'à 2 Go de taille"
Martin Smith
Un peu inutile mais intéressant. En théorie, vous pouvez remplir le disque avec une variable ... :-)
gbn
J'ai quelques hésitations ici car stocker une valeur dans une variable n'est pas la même chose que la stocker dans une colonne. Avez-vous plutôt envie d'essayer ceci avec une colonne - ou avez-vous une mise à jour? Même SQL Server 2000 peut avoir des varcharvaleurs de plus de 8 000 caractères dans les chaînes littérales du code, à condition que vous n'essayiez pas de le placer dans une variable ou une varcharcolonne.
ErikE
9

EDIT : Après une enquête plus approfondie, mon hypothèse initiale selon laquelle il s'agissait d'une anomalie (bogue?) De la declare @var datatype = valuesyntaxe est incorrecte.

J'ai modifié votre script pour 2005 car cette syntaxe n'est pas prise en charge, puis j'ai essayé la version modifiée en 2008. En 2005, j'obtiens le Attempting to grow LOB beyond maximum allowed size of 2147483647 bytes.message d'erreur. En 2008, le script modifié réussit toujours.

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
la source
Le script produit toujours une erreur (en essayant de faire l'insertion de table), mais en 2008, j'obtiens toujours un résultat dans le premier jeu de résultats, indiquant que la variable existe et est plus longue que 2 ^ 31-1.
Damien_The_Unbeliever
@Damien_The_Unbeliever: J'ai réduit le script à la partie variable et j'obtiens maintenant les mêmes résultats que vous. En 2005, j'obtiens l' Attempting to grow...erreur sur la set @GGMMsg=...déclaration. En 2008, le scénario est réussi.
Joe Stefanelli