[ Част 1 | Част 2 | Част 3 | Част 4 ]
Проблем, който видях да се появява няколко пъти напоследък, е сценарият, при който сте създали колона IDENTITY като INT и сега се доближавате до горната граница и трябва да я увеличите (BIGINT). Ако масата ви е достатъчно голяма, че достигате горната граница на цяло число (над 2 милиарда), това не е операция, която можете да извършите между обяда и кафе-паузата във вторник. Тази серия ще проучи механиката зад такава промяна и различни начини да я осъществите с различни въздействия върху времето на работа. В първата част исках да разгледам отблизо физическото въздействие от промяната на INT в BIGINT без нито една от другите променливи.
Какво всъщност се случва, когато разширите INT?
INT и BIGINT са типове данни с фиксиран размер, следователно преобразуването от един в друг трябва да докосне страницата, което прави това операция с размер на данни. Това е противоинтуитивно, защото изглежда, че не би било възможно промяната на типа данни от INT на BIGINT да изисква незабавно допълнително пространство на страницата (и за колона IDENTITY, винаги). Разсъждавайки логически, това е пространство, което не би могло да е необходимо до по-късно, когато съществуваща INT стойност беше променена на стойност> 4 байта. Но това не работи днес. Нека създадем проста таблица и да видим:
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
Една проста заявка може да ми каже ниската и високата страница, разпределена на този обект, както и общия брой страници:
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 );
Сега, ако изпълня тази заявка преди и след промяна на типа данни от INT на BIGINT:
ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint;
Виждам тези резултати:
-- before: lo_page hi_page page_count ------- ------- ---------- 243 303 17 -- after: lo_page hi_page page_count ------- ------- ---------- 243 319 33
Ясно е, че са добавени 16 нови страници, за да се освободи място за необходимото допълнително пространство (въпреки че знаем, че нито една от стойностите в таблицата всъщност не изисква 8 байта). Но това всъщност не беше постигнато по начина, по който може да си помислите – вместо да разширяват колоната на съществуващите страници, редовете бяха преместени на нови страници, като на тяхно място бяха оставени указатели. Разглеждайки страница 243 преди и след (с недокументираната 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
Тогава, ако погледнем целта на показалеца, страница 296, слот 376, виждаме:
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
Очевидно това е много разрушителна промяна в структурата на таблицата. (И интересно странично наблюдение:физическият ред на колоните, RowID и пълнителят, са обърнати на страницата.) Резервираното пространство скача от 136 KB на 264 KB, а средната фрагментация се увеличава скромно от 33,3% на 40%. Това пространство не се възстановява чрез повторно изграждане, онлайн или не, или реорганизация и – както ще видим скоро – това не е защото таблицата е твърде малка, за да се възползва.
Забележка:това е вярно дори и в най-новите версии на SQL Server 2016 – докато все повече и повече операции като тази са подобрени, за да станат операции само с метаданни в съвременните версии, тази все още не е коригирана, макар че е ясно може да бъде – отново, особено в случая, когато колоната е колона IDENTITY, която не може да бъде актуализирана по дефиниция.
Извършването на операцията с новия синтаксис ALTER COLUMN / ONLINE, за който говорих миналата година, дава някои разлики:
-- drop / re-create here ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint WITH (ONLINE = ON);
Сега преди и след става:
-- before: lo_page hi_page page_count ------- ------- ---------- 243 303 17 -- after: lo_page hi_page page_count ------- ------- ---------- 307 351 17
В този случай това все още беше операция с размер на данни, но съществуващите страници бяха копирани и създадени отново благодарение на опцията ОНЛАЙН. Може да се чудите защо, когато променихме размера на колоната като ОНЛАЙН операция, таблицата може да натъпче повече данни в същия брой страници? Всяка страница вече е по-плътна (по-малко редове, но повече данни на страница), с цената на разсейване – фрагментацията се удвоява от 33,3% на 66,7%. Използваното пространство показва повече данни в същото запазено пространство (от 72 KB / 136 KB до 96 KB / 136 KB).
И в по-голям мащаб?
Нека пуснем таблицата, да я създадем отново и да я попълним с много повече данни:
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;
От самото начало имаме 8 657 страници, ниво на фрагментация от 0,09%, а използваното пространство е 69 208 KB / 69 256 KB.
Ако променим типа данни на bigint, скачаме до 25 630 страници, фрагментацията се намалява до 0,06%, а използваното пространство е 205 032 KB / 205 064 KB. Онлайн повторното изграждане не променя нищо, нито реорганизацията. Целият процес, включително възстановяването, отнема около 97 секунди на моята машина (популацията от данни отне 2 секунди).
Ако променим типа данни на bigint, използвайки ОНЛАЙН, увеличението е само до 11 140 страници, фрагментацията стига до 85,5%, а използваното пространство е 89 088 KB / 89 160 KB. Онлайн реконструкциите и реорганизациите все още не променят нищо. Този път целият процес отнема само около минута. Така че новият синтаксис определено води до по-бързи операции и по-малко допълнително дисково пространство, но висока фрагментация. Ще го взема.
Напред
Сигурен съм, че разглеждате моите тестове по-горе и се чудите за няколко неща. Най-важното е защо масата е купчина? Исках да проуча какво всъщност се случва със структурата на страницата и броя на страниците без индекси, ключове или ограничения, които да размиват детайлите. Може също да се чудите защо тази промяна е толкова лесна – в сценарий, в който трябва да промените истинска колона IDENTITY, вероятно това е и клъстерираният първичен ключ и има зависимости от външни ключове в други таблици. Това определено внася някои затруднения в процеса. Ще разгледаме по-отблизо тези неща в следващата публикация от поредицата.
—
[ Част 1 | Част 2 | Част 3 | Част 4 ]