Maksymalny rozmiar zmiennej varchar (max)

89

Gdyby kiedykolwiek w przeszłości ktoś zapytał mnie o maksymalny rozmiar a varchar(max), powiedziałbym 2 GB lub szukał dokładniejszej liczby (2 ^ 31-1 lub 2147483647).

Jednak w niektórych niedawnych testach odkryłem, że varchar(max)zmienne mogą najwyraźniej przekraczać ten rozmiar:

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

Wyniki:

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

Biorąc więc pod uwagę, że teraz wiem, że zmienna może przekroczyć barierę 2 GB - czy ktoś wie, jaki jest faktyczny limit dla varchar(max)zmiennej?


(Powyższy test został ukończony na SQL Server 2008 (nie R2). Chciałbym wiedzieć, czy dotyczy to innych wersji)

Damien_The_Unbeliever
źródło
declare @x varchar(max) = 'XX'; SELECT LEN(REPLICATE(@x,2147483647))daje 4294967294mi, ale bieganie zajmuje dużo czasu - nawet po SELECTpowrocie, więc nie jestem pewien, na co ten dodatkowy czas spędza.
Martin Smith,

Odpowiedzi:

74

O ile wiem, w 2008 roku nie ma górnej granicy.

W SQL Server 2005 kod w twoim pytaniu nie powiedzie się przy przypisaniu do @GGMMsgzmiennej z

Próba zwiększenia LOB poza maksymalny dozwolony rozmiar 2 147 483 647 bajtów.

poniższy kod nie działa

REPLICATE: długość wyniku przekracza limit długości (2 GB) docelowego dużego typu.

Jednak wydaje się, że ograniczenia te zostały po cichu zniesione. W 2008r

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

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

SELECT LEN(@y) 

Zwroty

8589767761

Uruchomiłem to na moim 32-bitowym komputerze stacjonarnym, więc ten ciąg 8 GB jest znacznie większy niż pamięć adresowalna

Bieganie

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

Zwrócony

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

więc zakładam, że to wszystko jest po prostu zapisywane na LOBstronach tempdbbez sprawdzania długości. Wzrost liczby stron był związany ze SET @y = REPLICATE(@y,92681);stwierdzeniem. Początkowe przypisanie zmiennej do @yi LENobliczenia nie zwiększyły tego.

Powodem, dla którego o tym wspominam, jest to, że liczba stron jest znacznie większa, niż się spodziewałem. Zakładając, że strona ma 8 KB, wychodzi to na 16,36 GB, co jest oczywiście mniej więcej dwa razy większe niż wydaje się konieczne. Spekuluję, że jest to prawdopodobnie spowodowane nieefektywnością operacji łączenia ciągów, która wymaga skopiowania całego ogromnego ciągu i dołączenia fragmentu na końcu zamiast możliwości dodania go na końcu istniejącego ciągu. Niestety w tej chwili .WRITEmetoda nie jest obsługiwana dla zmiennych varchar (max).

Dodanie

Przetestowałem również zachowanie z łączeniem nvarchar(max) + nvarchar(max)i nvarchar(max) + varchar(max). Oba pozwalają na przekroczenie limitu 2 GB. Próba zapisania wyników w tabeli kończy się jednak niepowodzeniem i Attempting to grow LOB beyond maximum allowed size of 2147483647 bytes.ponownie pojawia się komunikat o błędzie . Skrypt do tego znajduje się poniżej (może zająć dużo czasu).

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
źródło
1
Doskonale - więc mogłoby się wydawać, że dokumentacja jest raczej „niekompletna” - zauważam, że zwykła strona odnosi się do maksymalnego „rozmiaru pamięci”, który przypuszczalnie dotyczy tylko kolumn, a nie zmiennych.
Damien_The_Unbeliever
@Damien - Zdecydowanie tak się pojawia. Nie jestem pewien, czy istnieje jakiś inny limit, który można osiągnąć pod względem całkowitej liczby stron, ale myślę, że jest on przechowywany w strukturze drzewa B (na podstawie s. 381 wewnętrznych elementów SQL Server 2008), więc w zasadzie można go zdecydowanie rozszerzyć.
Martin Smith,
@Damien_The_Unbeliever - Dokumentacja tutaj wydaje się być błędna na podstawie przeprowadzonych tutaj eksperymentów, stwierdzając dość jednoznacznie, że „zmienne i parametry typu danych dużego obiektu (LOB) ... typy mogą mieć rozmiar do 2 GB”
Martin Smith
Trochę bezużyteczne, ale interesujące. Teoretycznie można by zapełnić dysk jedną zmienną ... :-)
gbn
Mam tu pewne wahanie, ponieważ przechowywanie wartości w zmiennej to nie to samo, co przechowywanie jej w kolumnie. Czy masz ochotę spróbować zamiast tego z kolumną - czy masz aktualizację? Nawet SQL Server 2000 mógł mieć varcharwartości dłuższe niż 8000 znaków w literałowych ciągach znaków w kodzie, o ile nie próbowałeś umieszczać ich w zmiennej lub varcharkolumnie.
ErikE
9

EDYCJA : Po dalszych badaniach moje pierwotne założenie, że była to anomalia (błąd?) W declare @var datatype = valueskładni, jest niepoprawne.

Zmodyfikowałem twój skrypt na rok 2005, ponieważ ta składnia nie jest obsługiwana, a następnie wypróbowałem zmodyfikowaną wersję w roku 2008. W 2005 pojawia się Attempting to grow LOB beyond maximum allowed size of 2147483647 bytes.komunikat o błędzie. W 2008 roku zmodyfikowany skrypt nadal odnosi sukcesy.

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
źródło
Skrypt zawsze generuje błąd (próbując wstawić tabelę), ale w 2008 roku zawsze otrzymuję jeden wynik w pierwszym zestawie wyników, wskazujący, że zmienna istnieje i jest dłuższa niż 2 ^ 31-1.
Damien_The_Unbeliever
@Damien_The_Unbeliever: Ograniczam skrypt do części zmiennej i teraz otrzymuję takie same wyniki jak ty. W 2005 roku pojawia się Attempting to grow...błąd w set @GGMMsg=...wyciągu. W 2008 roku scenariusz się udał.
Joe Stefanelli