[ Część 1 | Część 2 | Część 3 | Część 4 ]
Problem, który pojawił się ostatnio kilka razy, to scenariusz, w którym utworzyłeś kolumnę IDENTITY jako INT, a teraz zbliżasz się do górnej granicy i musisz ją powiększyć (BIGINT). Jeśli Twój stół jest na tyle duży, że osiągasz górną granicę liczby całkowitej (ponad 2 miliardy), nie jest to operacja, którą możesz wykonać między obiadem a przerwą na kawę we wtorek. W tej serii omówimy mechanikę takiej zmiany i różne sposoby jej wprowadzenia z różnym wpływem na czas pracy bez przestojów. W pierwszej części chciałem przyjrzeć się z bliska fizycznemu wpływowi zmiany INT na BIGINT bez innych zmiennych.
Co się właściwie dzieje, gdy poszerzasz INT?
INT i BIGINT są typami danych o stałym rozmiarze, dlatego konwersja z jednego do drugiego musi dotykać strony, co czyni tę operację wielkością danych. Jest to sprzeczne z intuicją, ponieważ wydaje się, że nie byłoby możliwe, aby zmiana typu danych z INT na BIGINT wymagała natychmiastowego dodatkowego miejsca na stronie (i kiedykolwiek dla kolumny IDENTITY). Myśląc logicznie, jest to przestrzeń, która prawdopodobnie nie będzie potrzebna do czasu, gdy istniejąca wartość INT zostanie zmieniona na wartość> 4 bajty. Ale dziś tak to nie działa. Stwórzmy prostą tabelę i zobaczmy:
CREATE TABLE dbo.FirstTest ( RowID int IDENTITY(1,1), Filler char(2500) NOT NULL DEFAULT 'x' ); GO INSERT dbo.FirstTest WITH (TABLOCKX) (Filler) SELECT TOP (20) 'x' FROM sys.all_columns AS c; GO
Proste zapytanie może mi podać dolną i górną stronę przydzieloną do tego obiektu, a także całkowitą liczbę stron:
SELECT lo_page = MIN(allocated_page_page_id), hi_page = MAX(allocated_page_page_id), page_count = COUNT(*) FROM sys.dm_db_database_page_allocations ( DB_ID(), OBJECT_ID(N'dbo.FirstTest'), NULL, NULL, NULL );
Teraz, jeśli uruchomię to zapytanie przed i po zmianie typu danych z INT na BIGINT:
ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint;
Widzę te wyniki:
-- before: lo_page hi_page page_count ------- ------- ---------- 243 303 17 -- after: lo_page hi_page page_count ------- ------- ---------- 243 319 33
Oczywiste jest, że dodano 16 nowych stron, aby zrobić miejsce na wymagane dodatkowe miejsce (mimo że wiemy, że żadna z wartości w tabeli nie wymaga w rzeczywistości 8 bajtów). Ale nie udało się tego osiągnąć w sposób, w jaki można by sądzić – zamiast poszerzyć kolumnę na istniejących stronach, wiersze zostały przeniesione na nowe strony, z pozostawionymi wskaźnikami. Patrząc na stronę 243 przed i po (z nieudokumentowaną DBCC PAGE
) ):
-- ******** Page 243, before: ******** Slot 0 Offset 0x60 Length 12 Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 12 Memory Dump @0x000000E34B9FA060 0000000000000000: 10000900 01000000 78020000 .. .....x... Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4 RowID = 1 Slot 0 Column 2 Offset 0x8 Length 1 Length (physical) 1 filler = x -- ******** Page 243, after: ******** Slot 0 Offset 0x60 Length 9 Record Type = FORWARDING_STUB Record Attributes = Record Size = 9 Memory Dump @0x000000E34B9FA060 0000000000000000: 04280100 00010078 01 .(.....x. Forwarding to = file 1 page 296 slot 376
Następnie, jeśli spojrzymy na cel wskaźnika, strona 296, szczelina 376, zobaczymy:
Slot 376 Offset 0x8ca Length 34 Record Type = FORWARDED_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 34 Memory Dump @0x000000E33BBFA8CA 0000000000000000: 32001100 01000000 78010000 00000000 00030000 2.......x........... 0000000000000014: 01002280 0004f300 00000100 0000 .."...ó....... Forwarded from = file 1 page 243 slot 0 Slot 376 Column 67108865 Offset 0x4 Length 0 Length (physical) 4 DROPPED = NULL Slot 376 Column 2 Offset 0x8 Length 1 Length (physical) 1 filler = x Slot 376 Column 1 Offset 0x9 Length 8 Length (physical) 8 RowID = 1
To oczywiście bardzo destrukcyjna zmiana w strukturze tabeli. (I ciekawa obserwacja poboczna:fizyczna kolejność kolumn, RowID i wypełniacz, zostały odwrócone na stronie.) Zarezerwowane miejsce skacze z 136 KB do 264 KB, a średnia fragmentacja wzrasta nieznacznie z 33,3% do 40%. Ta przestrzeń nie jest odzyskiwana przez przebudowę, online lub nie, ani przez reorg, i – jak wkrótce zobaczymy – nie dzieje się tak dlatego, że tabela jest zbyt mała, aby z niej skorzystać.
Uwaga:dotyczy to nawet najnowszych kompilacji SQL Server 2016 – chociaż coraz więcej takich operacji zostało ulepszonych, aby w nowoczesnych wersjach stały się operacjami tylko na metadanych, ta nie została jeszcze naprawiona, choć wyraźnie może tak być – znowu, szczególnie w przypadku, gdy kolumna jest kolumną IDENTITY, która z definicji nie może być aktualizowana.
Wykonanie operacji z nową składnią ALTER COLUMN / ONLINE, o której mówiłem w zeszłym roku, daje pewne różnice:
-- drop / re-create here ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint WITH (ONLINE = ON);
Teraz „przed” i „po” staje się:
-- before: lo_page hi_page page_count ------- ------- ---------- 243 303 17 -- after: lo_page hi_page page_count ------- ------- ---------- 307 351 17
W tym przypadku była to nadal operacja rozmiaru danych, ale istniejące strony zostały skopiowane i odtworzone dzięki opcji ONLINE. Możesz się zastanawiać, dlaczego, kiedy zmieniliśmy rozmiar kolumny jako operację ONLINE, tabela jest w stanie upchnąć więcej danych na tej samej liczbie stron? Każda strona jest teraz gęstsza (mniej wierszy, ale więcej danych na stronie), kosztem rozproszenia – fragmentacja podwaja się z 33,3% do 66,7%. Wykorzystana przestrzeń pokazuje więcej danych w tej samej zarezerwowanej przestrzeni (od 72 KB / 136 KB do 96 KB / 136 KB).
A na większą skalę?
Upuśćmy tabelę, utwórzmy ją ponownie i wypełnijmy dużo większą ilością danych:
CREATE TABLE dbo.FirstTest ( RowID INT IDENTITY(1,1), filler CHAR(1) NOT NULL DEFAULT 'x' ); GO INSERT dbo.FirstTest WITH (TABLOCKX) (filler) SELECT TOP (5000000) 'x' FROM sys.all_columns AS c1 CROSS JOIN sys.all_columns AS c2;
Od początku mamy teraz 8657 stron, poziom fragmentacji 0,09%, a wykorzystane miejsce to 69 208 KB / 69 256 KB.
Jeśli zmienimy typ danych na bigint, przeskoczymy do 25 630 stron, fragmentacja zostanie zmniejszona do 0,06%, a wykorzystane miejsce to 205 032 KB / 205 064 KB. Przebudowa online niczego nie zmienia, podobnie jak reorg. Cały proces, w tym odbudowa, zajmuje na moim komputerze około 97 sekund (populacja danych zajęła całe 2 sekundy).
Jeśli zmienimy typ danych na bigint za pomocą ONLINE, podbicie wynosi tylko 11 140 stron, fragmentacja sięga 85,5%, a wykorzystane miejsce to 89 088 KB / 89160 KB. Przebudowy i reorganizacje online nadal niczego nie zmieniają. Tym razem cały proces zajmuje tylko około minuty. Tak więc nowa składnia zdecydowanie prowadzi do szybszych operacji i mniejszej ilości dodatkowego miejsca na dysku, ale dużej fragmentacji. Wezmę to.
Następny
Jestem pewien, że patrzysz na moje testy powyżej i zastanawiasz się nad kilkoma rzeczami. Co najważniejsze, dlaczego stół jest kupą? Chciałem zbadać, co faktycznie dzieje się ze strukturą strony i liczbą stron bez indeksów, kluczy lub ograniczeń, które rozmywają szczegóły. Możesz również zastanawiać się, dlaczego ta zmiana była tak łatwa — w scenariuszu, w którym musisz zmienić kolumnę true IDENTITY, prawdopodobnie jest to również klastrowany klucz podstawowy i ma zależności kluczy obcych w innych tabelach. To zdecydowanie wprowadza pewne czkawki do tego procesu. Przyjrzymy się tym bliżej w następnym poście z tej serii.
—
[ Część 1 | Część 2 | Część 3 | Część 4 ]