Database
 sql >> база данни >  >> RDS >> Database

Минимизиране на въздействието от разширяването на колона IDENTITY – част 1

[ Част 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 ]


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SQL Equals (=) Оператор за начинаещи

  2. Модел на база данни за онлайн проучване. Част 1

  3. Свързване на F# към Salesforce.com

  4. Как да автоматизирате задачите за поддръжка на база данни на SQL с помощта на SQLCMD

  5. Вмъкване на DML с променлива за свързване:ИЗПОЛЗВАНЕ Клауза за изпълнение на незабавно изявление