[ Deel 1 | Deel 2 | Deel 3 | Deel 4 ]
Een probleem dat ik onlangs een paar keer heb zien opduiken, is het scenario waarin je een IDENTITY-kolom als een INT hebt gemaakt en nu de bovengrens nadert en deze groter moet maken (BIGINT). Als uw tafel zo groot is dat u de bovengrens van een geheel getal bereikt (meer dan 2 miljard), is dit geen bewerking die u kunt uitvoeren tussen de lunch en uw koffiepauze op dinsdag. Deze serie onderzoekt de mechanica achter een dergelijke verandering en verschillende manieren om dit te laten gebeuren met verschillende gevolgen voor de uptime. In het eerste deel wilde ik de fysieke impact bekijken van het veranderen van een INT naar een BIGINT zonder een van de andere variabelen.
Wat gebeurt er eigenlijk als je een INT verbreedt?
INT en BIGINT zijn gegevenstypen met een vaste grootte, daarom moet een conversie van de ene naar de andere de pagina raken, waardoor dit een bewerking van gegevensgrootte is. Dit is contra-intuïtief, omdat het lijkt alsof het niet mogelijk zou zijn voor een wijziging van het gegevenstype van INT naar BIGINT om de extra ruimte op de pagina onmiddellijk te vereisen (en voor een IDENTITY-kolom, ooit). Logisch gezien is dit ruimte die pas later nodig zou kunnen zijn, wanneer een bestaande INT-waarde werd gewijzigd in een waarde> 4 bytes. Maar zo werkt het tegenwoordig niet. Laten we een eenvoudige tabel maken en kijken:
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
Een eenvoudige zoekopdracht kan me de lage en hoge pagina vertellen die aan dit object is toegewezen, evenals het totale aantal pagina's:
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 );
Als ik die query nu uitvoer voor en na het wijzigen van het gegevenstype van INT in BIGINT:
ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint;
Ik zie deze resultaten:
-- before: lo_page hi_page page_count ------- ------- ---------- 243 303 17 -- after: lo_page hi_page page_count ------- ------- ---------- 243 319 33
Het is duidelijk dat er 16 nieuwe pagina's zijn toegevoegd om ruimte te maken voor de extra benodigde ruimte (hoewel we weten dat geen van de waarden in de tabel 8 bytes nodig heeft). Maar dit werd niet echt bereikt zoals je zou denken - in plaats van de kolom op de bestaande pagina's te verbreden, werden de rijen verplaatst naar nieuwe pagina's, waarbij de wijzers op hun plaats bleven. Kijkend naar pagina 243 voor en na (met de ongedocumenteerde 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
Als we dan naar het doel van de aanwijzer kijken, pagina 296, slot 376, zien we:
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
Dit is natuurlijk een zeer storende verandering in de structuur van de tabel. (En een interessante kanttekening:de fysieke volgorde van de kolommen, RowID en filler, is op de pagina omgedraaid.) Gereserveerde ruimte springt van 136 KB naar 264 KB, en de gemiddelde fragmentatie stijgt bescheiden van 33,3% naar 40%. Deze ruimte wordt niet teruggewonnen door een verbouwing, al dan niet online, of een reorganisatie, en - zoals we binnenkort zullen zien - is dit niet omdat de tafel te klein is om ervan te profiteren.
Opmerking:dit geldt zelfs in de meest recente builds van SQL Server 2016 - hoewel steeds meer bewerkingen zoals deze zijn verbeterd om alleen metadata-bewerkingen te worden in moderne versies, is deze nog niet opgelost, hoewel duidelijk het zou kunnen - nogmaals, vooral in het geval dat de kolom een IDENTITEIT-kolom is, die niet per definitie kan worden bijgewerkt.
Het uitvoeren van de bewerking met de nieuwe ALTER COLUMN / ONLINE-syntaxis, waar ik vorig jaar over sprak, levert enkele verschillen op:
-- drop / re-create here ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint WITH (ONLINE = ON);
Nu wordt het voor en na:
-- before: lo_page hi_page page_count ------- ------- ---------- 243 303 17 -- after: lo_page hi_page page_count ------- ------- ---------- 307 351 17
In dit geval was het nog steeds een bewerking van de gegevensgrootte, maar de bestaande pagina's werden gekopieerd en opnieuw gemaakt dankzij de ONLINE-optie. U vraagt zich misschien af waarom, wanneer we de kolomgrootte hebben gewijzigd als een ONLINE-bewerking, de tabel meer gegevens op hetzelfde aantal pagina's kan proppen? Elke pagina is nu dichter (minder rijen maar meer gegevens per pagina), ten koste van spreiding - de fragmentatie verdubbelt van 33,3% tot 66,7%. Gebruikte ruimte toont meer gegevens in dezelfde gereserveerde ruimte (van 72 KB / 136 KB tot 96 KB / 136 KB).
En op grotere schaal?
Laten we de tabel laten vallen, opnieuw maken en vullen met veel meer gegevens:
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;
Vanaf het begin hebben we nu 8.657 pagina's, een fragmentatieniveau van 0,09% en de gebruikte ruimte is 69.208 KB / 69.256 KB.
Als we het gegevenstype wijzigen in bigint, springen we naar 25.630 pagina's, wordt de fragmentatie teruggebracht tot 0,06% en is de gebruikte ruimte 205.032 KB / 205.064 KB. Een online rebuild verandert niets, en een reorganisatie ook niet. Het hele proces, inclusief het opnieuw opbouwen, duurt ongeveer 97 seconden op mijn computer (de gegevenspopulatie duurde maar liefst 2 seconden).
Als we het gegevenstype wijzigen in bigint met ONLINE, is de stijging slechts 11.140 pagina's, gaat de fragmentatie naar 85,5% en is de gebruikte ruimte 89.088 KB / 89160 KB. Online rebuilds en reorgs veranderen nog steeds niets. Deze keer duurt het hele proces slechts ongeveer een minuut. De nieuwe syntaxis leidt dus zeker tot snellere bewerkingen en minder extra schijfruimte, maar tot hoge fragmentatie. Ik neem het.
Volgende
Ik weet zeker dat je naar mijn tests hierboven kijkt en je een paar dingen afvraagt. Het belangrijkste is:waarom is de tafel een hoop? Ik wilde onderzoeken wat er werkelijk gebeurt met de paginastructuur en het aantal pagina's zonder indexen, sleutels of beperkingen die de details vertroebelen. Je kunt je ook afvragen waarom deze wijziging zo eenvoudig was:in een scenario waarin je een echte IDENTITY-kolom moet wijzigen, is dit waarschijnlijk ook de geclusterde primaire sleutel en heeft deze externe sleutelafhankelijkheden in andere tabellen. Dit introduceert zeker enkele haperingen in het proces. We zullen deze dingen nader bekijken in de volgende post in de serie.
—
[ Deel 1 | Deel 2 | Deel 3 | Deel 4 ]