sql >> Databasteknik >  >> RDS >> Database

Minimera effekten av att bredda en IDENTITY-kolumn – del 1

[ Del 1 | Del 2 | Del 3 | Del 4 ]

Ett problem som jag har sett dyka upp några gånger nyligen är scenariot där du har skapat en IDENTITY-kolumn som en INT, och nu närmar dig den övre gränsen och behöver göra den större (BIGINT). Om ditt bord är tillräckligt stort för att du når den övre gränsen för ett heltal (över 2 miljarder), är detta inte en operation du kan genomföra mellan lunchen och din fikapaus på en tisdag. Den här serien kommer att utforska mekaniken bakom en sådan förändring, och olika sätt att få det att hända med varierande inverkan på drifttiden. I den första delen ville jag ta en närmare titt på den fysiska effekten av att ändra en INT till en BIGINT utan någon av de andra variablerna.

Vad händer egentligen när du breddar en INT?

INT och BIGINT är datatyper med fast storlek, därför måste en konvertering från den ena till den andra röra sidan, vilket gör detta till en datastorleksoperation. Detta är kontraintuitivt, eftersom det verkar som om det inte skulle vara möjligt för en datatypsändring från INT till BIGINT att kräva extra utrymme på sidan omedelbart (och för en IDENTITY-kolumn, någonsin). Om man tänker logiskt är detta utrymme som omöjligt kunde behövas förrän senare, när ett befintligt INT-värde ändrades till ett värde> 4 byte. Men det är inte så det fungerar idag. Låt oss skapa en enkel tabell och se:

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

En enkel fråga kan berätta för mig den låga och höga sidan som tilldelats detta objekt, såväl som det totala antalet sidor:

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

Om jag nu kör den frågan före och efter att ha ändrat datatypen från INT till BIGINT:

ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint;

Jag ser dessa resultat:

-- before:
 
lo_page    hi_page    page_count
-------    -------    ----------
243        303        17
 
-- after:
 
lo_page    hi_page    page_count
-------    -------    ----------
243        319        33

Det är tydligt att 16 nya sidor lades till för att ge plats åt det extra utrymme som krävs (även om vi vet att inget av värdena i tabellen faktiskt kräver 8 byte). Men detta genomfördes faktiskt inte som du kanske tror – snarare än att bredda kolumnen på de befintliga sidorna flyttades raderna till nya sidor, med pekare kvar i deras ställe. Tittar på sidan 243 före och efter (med den odokumenterade 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

Om vi ​​sedan tittar på pekarens mål, sida 296, lucka 376, ser vi:

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

Detta är uppenbarligen en mycket störande förändring av tabellens struktur. (Och en intressant sidoobservation:den fysiska ordningen för kolumnerna, RowID och filler, har vänts på sidan.) Reserverat utrymme hoppar från 136 KB till 264 KB, och den genomsnittliga fragmenteringen ökar blygsamt från 33,3 % till 40 %. Det här utrymmet återvinns inte av en ombyggnad, online eller inte, eller en omorganisation, och – som vi snart kommer att se – det beror inte på att bordet är för litet för att gynnas.

Obs:detta är sant även i de senaste versionerna av SQL Server 2016 – medan fler och fler operationer som denna har förbättrats för att bli operationer som endast är metadata i moderna versioner, har den här inte åtgärdats ännu, även om det är klart det kan vara – återigen, särskilt i de fall då kolumnen är en IDENTITY-kolumn, som inte kan uppdateras per definition.

Att utföra operationen med den nya syntaxen ALTER COLUMN / ONLINE, som jag pratade om förra året, ger några skillnader:

-- drop / re-create here
ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint WITH (ONLINE = ON);

Nu blir före och efter:

-- before:
 
lo_page    hi_page    page_count
-------    -------    ----------
243        303        17
 
-- after:
 
lo_page    hi_page    page_count
-------    -------    ----------
307        351        17

I det här fallet var det fortfarande en datastorleksoperation, men de befintliga sidorna kopierades och återskapades på grund av ONLINE-alternativet. Du kanske undrar varför, när vi ändrade kolumnstorleken som en ONLINE-operation, kan tabellen stoppa in mer data på samma antal sidor? Varje sida är nu tätare (färre rader men mer data per sida), till priset av spridning – fragmenteringen fördubblas från 33,3 % till 66,7 %. Använd utrymme visar mer data på samma reserverade utrymme (från 72 KB / 136 KB till 96 KB / 136 KB).

Och i större skala?

Låt oss släppa tabellen, återskapa den och fylla i den med mycket mer data:

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;

Från början har vi nu 8 657 sidor, en fragmenteringsnivå på 0,09 % och utrymmet som används är 69 208 KB / 69 256 KB.

Om vi ​​ändrar datatypen till bigint hoppar vi till 25 630 sidor, fragmenteringen reduceras till 0,06 % och utrymmet som används är 205 032 KB / 205 064 KB. En online-ombyggnad förändrar ingenting, inte heller en omorganisation. Hela processen, inklusive en ombyggnad, tar cirka 97 sekunder på min dator (datapopulationen tog hela 2 sekunder).

Om vi ​​ändrar datatypen till bigint med ONLINE, är bumpen endast till 11 140 sidor, fragmenteringen går till 85,5 % och utrymmet som används är 89 088 KB / 89 160 KB. Online ombyggnader och reorganiseringar förändrar fortfarande ingenting. Den här gången tar hela processen bara ungefär en minut. Så den nya syntaxen leder definitivt till snabbare operationer och mindre extra diskutrymme, men hög fragmentering. Jag tar det.

Nästa

Jag är säker på att du tittar på mina tester ovan och undrar över några saker. Viktigast av allt, varför är bordet en hög? Jag ville undersöka vad som faktiskt händer med sidstrukturen och sidantal utan att index, nycklar eller begränsningar förtydligar detaljerna. Du kanske också undrar varför denna förändring var så enkel – i ett scenario där du måste ändra en sann IDENTITY-kolumn, är det förmodligen också den klustrade primärnyckeln och har beroenden av främmande nyckel i andra tabeller. Detta introducerar definitivt vissa hicka i processen. Vi ska titta närmare på dessa saker i nästa inlägg i serien.

[ Del 1 | Del 2 | Del 3 | Del 4 ]


  1. problem med to_date-funktionen med sysdate

  2. Android-rumsdatabasen exporterar inte all data

  3. Exportera en MySQL-databas till SQLite-databas

  4. Gå med SQL Server-drivrutinen kan inte ansluta, inloggningen misslyckades