sql >> Databáze >  >> RDS >> Database

Minimalizace dopadu rozšíření sloupce IDENTITY – část 1

[ Část 1 | Část 2 | Část 3 | Část 4 ]

Problém, který jsem v poslední době několikrát viděl, je scénář, kdy jste vytvořili sloupec IDENTITY jako INT a nyní se blížíte k horní hranici a potřebujete jej zvětšit (BIGINT). Pokud je váš stůl dostatečně velký, abyste dosáhli horní hranice celého čísla (přes 2 miliardy), není to operace, kterou můžete provést mezi obědem a přestávkou na kávu v úterý. Tato série prozkoumá mechanismy, které stojí za takovou změnou, a různé způsoby, jak ji provést, s různým dopadem na dobu provozu. V první části jsem se chtěl zblízka podívat na fyzický dopad změny INT na BIGINT bez jakýchkoli dalších proměnných.

Co se vlastně stane, když rozšíříte INT?

INT a BIGINT jsou datové typy s pevnou velikostí, takže převod z jednoho na druhý se musí dotknout stránky, takže jde o operaci velikosti dat. To je kontraintuitivní, protože se zdá, že by nebylo možné, aby změna datového typu z INT na BIGINT vyžadovala dodatečné místo na stránce okamžitě (a vůbec pro sloupec IDENTITY). Logicky se jedná o prostor, který by mohl být potřeba až později, kdy byla stávající hodnota INT změněna na hodnotu> 4 bajty. Ale takhle to dnes nefunguje. Vytvoříme jednoduchou tabulku a uvidíme:

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

Jednoduchý dotaz mi může sdělit nízkou a vysokou stránku přidělenou tomuto objektu a také celkový počet stránek:

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

Nyní, když spustím tento dotaz před a po změně typu dat z INT na BIGINT:

ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint;

Vidím tyto výsledky:

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

Je jasné, že bylo přidáno 16 nových stránek, aby se vytvořil prostor pro další požadovaný prostor (i když víme, že žádná z hodnot v tabulce ve skutečnosti nevyžaduje 8 bajtů). Ve skutečnosti to však nebylo provedeno tak, jak byste si mohli myslet – namísto rozšíření sloupce na stávajících stránkách byly řádky přesunuty na nové stránky a na jejich místě zůstaly ukazatele. Pohled na stránku 243 před a za (s nezdokumentovaným 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

Když se pak podíváme na cíl ukazatele, stránka 296, slot 376, uvidíme:

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 je samozřejmě velmi rušivá změna struktury tabulky. (A zajímavý vedlejší postřeh:fyzické pořadí sloupců, RowID a výplň, bylo na stránce překlopeno.) Rezervovaný prostor vyskočí ze 136 KB na 264 KB a průměrná fragmentace se mírně zvýší z 33,3 % na 40 %. Tento prostor se neobnoví přestavbou, ať už online nebo ne, nebo přeorganizací, a – jak brzy uvidíme – není to proto, že by stůl byl příliš malý, aby z něj mohl těžit.

Poznámka:To platí i pro nejnovější sestavení SQL Server 2016 – zatímco stále více operací, jako je tato, bylo vylepšováno tak, aby se v moderních verzích staly operacemi pouze s metadaty, tato dosud nebyla opravena, i když je zřejmé, že může být – opět, zejména v případě, kdy je sloupcem sloupec IDENTITY, který nelze z definice aktualizovat.

Provedení operace s novou syntaxí ALTER COLUMN / ONLINE, o které jsem mluvil minulý rok, přináší určité rozdíly:

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

Nyní se před a po stává:

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

V tomto případě se stále jednalo o operaci velikosti dat, ale stávající stránky byly zkopírovány a znovu vytvořeny díky možnosti ONLINE. Možná se divíte, proč když jsme změnili velikost sloupce jako ONLINE operaci, tabulka je schopna nacpat více dat na stejný počet stránek? Každá stránka je nyní hustší (méně řádků, ale více dat na stránku), za cenu rozptylu – fragmentace se zdvojnásobila z 33,3 % na 66,7 %. Použitý prostor zobrazuje více dat ve stejném vyhrazeném prostoru (od 72 KB / 136 KB do 96 KB / 136 KB).

A ve větším měřítku?

Pojďme tabulku zahodit, vytvořit ji znovu a naplnit ji mnohem více daty:

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 začátku máme nyní 8 657 stránek, úroveň fragmentace 0,09 % a využitý prostor je 69 208 KB / 69 256 KB.

Pokud změníme datový typ na bigint, skočíme na 25 630 stránek, fragmentace se sníží na 0,06 % a využitý prostor je 205 032 KB / 205 064 KB. Online přestavba nic nezmění, stejně jako reorg. Celý proces, včetně přestavby, trvá na mém počítači asi 97 sekund (obsazení dat trvalo 2 sekundy).

Pokud změníme datový typ na bigint pomocí ONLINE, náraz je pouze na 11 140 stránek, fragmentace na 85,5 % a využitý prostor je 89 088 KB / 89 160 KB. Online přestavby a reorganizace stále nic nemění. Tentokrát celý proces trvá jen asi minutu. Nová syntaxe tedy rozhodně vede k rychlejším operacím a menšímu dodatečnému prostoru na disku, ale vysoké fragmentaci. Vezmu to.

Další

Jsem si jistý, že se díváte na mé testy výše a zajímá vás několik věcí. A co je nejdůležitější, proč je stůl hromada? Chtěl jsem prozkoumat, co se vlastně stane se strukturou stránek a počtem stránek, aniž by do detailů zasahovaly žádné indexy, klíče nebo omezení. Možná se také divíte, proč byla tato změna tak snadná – ve scénáři, kde musíte změnit skutečný sloupec IDENTITY, je to pravděpodobně také seskupený primární klíč a má závislosti na cizím klíči v jiných tabulkách. To rozhodně vnáší do procesu nějaké škytavky. Na tyto věci se blíže podíváme v dalším příspěvku v seriálu.

[ Část 1 | Část 2 | Část 3 | Část 4 ]


  1. SQLSTATE[HY093]:Neplatné číslo parametru:parametr nebyl definován

  2. Nastavení názvu aplikace na Postgres/SQLAlchemy

  3. Jak zálohovat databáze MySQL pomocí AutoMySQLBackup

  4. Automatizujte obnovu testů databáze v SQL Server