it-swarm-eu.dev

Proč ALTER COLUMN NOT NULL způsobuje masivní růst souborů protokolu?

Mám tabulku s 64 m řádky, která vezme 4,3 GB na disk za svá data.

Každý řádek obsahuje asi 30 bajtů celých sloupců plus sloupec proměnné NVARCHAR(255) pro text.

Přidal jsem NULLABLE sloupec s datovým typem Datetimeoffset(0).

Poté jsem aktualizoval tento sloupec pro každý řádek a ujistil jsem se, že všechny nové vložení vloží do tohoto sloupce hodnotu.

Jakmile neexistovaly žádné položky NULL, spustil jsem tento příkaz, aby bylo moje nové pole povinné:

ALTER TABLE tblCheckResult 
ALTER COLUMN [dtoDateTime] [datetimeoffset](0) NOT NULL

Výsledkem byl obrovský nárůst ve velikosti protokolu transakcí - ze 6 GB na více než 36 GB, až do vyčerpání místa!

Má někdo tušení, co na světě SQL Server 2008 R2 dělá, aby tento jednoduchý příkaz vyústil v tak obrovský růst?

56
PapillonUK

Když změníte sloupec na NOT NULL, SQL Server se musí dotknout každé jediné stránky, i když neexistují žádné hodnoty NULL. V závislosti na faktoru výplně by to ve skutečnosti mohlo vést k velkému rozdělení stránek. Každá stránka, která se dotkne, musí být samozřejmě protokolována a mám podezření z důvodu mezer, že u mnoha stránek bude pravděpodobně nutné zaznamenat dvě změny. Protože je to všechno provedeno v jednom průchodu, protokol musí brát v úvahu všechny změny, takže pokud stisknete zrušit, bude přesně vědět, co vrátit.


Příklad. Jednoduchá tabulka:

DROP TABLE dbo.floob;
GO

CREATE TABLE dbo.floob
(
  id INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED, 
  bar INT NULL
);

INSERT dbo.floob(bar) SELECT NULL UNION ALL SELECT 4 UNION ALL SELECT NULL;

ALTER TABLE dbo.floob ADD CONSTRAINT df DEFAULT(0) FOR bar

Nyní se podívejme na podrobnosti stránky. Nejprve musíme zjistit, s jakou stránkou a DB_ID se zabýváme. V mém případě jsem vytvořil databázi s názvem foo a DB_ID se stalo 5.

DBCC TRACEON(3604, -1);
DBCC IND('foo', 'dbo.floob', 1);
SELECT DB_ID();

Výstup naznačil, že mě zajímá stránka 159 (jediný řádek v DBCC IND výstup s PageType = 1).

Nyní se podíváme na některé podrobnosti vybrané stránky, když projdeme scénář OP.

DBCC PAGE(5, 1, 159, 3);

enter image description here

UPDATE dbo.floob SET bar = 0 WHERE bar IS NULL;    
DBCC PAGE(5, 1, 159, 3);

enter image description here

ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;
DBCC PAGE(5, 1, 159, 3);

enter image description here

Teď na to nemám všechny odpovědi, protože nejsem hluboký interní chlap. Je však jasné, že - zatímco operace aktualizace i přidání omezení NENÍ NULL nepopiratelně zapisují na stránku - tato stránka to dělá úplně jiným způsobem. Zdá se, že ve skutečnosti mění strukturu záznamu, spíše než se jen pohrává s bity, tím, že zaměňuje nullový sloupec za nullový sloupec. Proč to musí udělat, nejsem si zcela jistý - dobrá otázka pro tým úložného motor , myslím. Věřím, že SQL Server 2012 některé z těchto scénářů zvládne mnohem lépe, FWIW - ale ještě musím provést vyčerpávající testování.

48
Aaron Bertrand

Při provádění příkazu

ALTER COLUMN ... NOT NULL

Zdá se, že je implementována jako operace Add Column, Update, Drop Column.

  • Do sys.sysrscols Je vložen nový řádek, který představuje nový sloupec. Je nastaven bit status pro 128, Což naznačuje, že sloupec neumožňuje NULLs
  • Aktualizace se provádí na každém řádku tabulky a nastavuje novou hodnotu sloupce na hodnotu původní hodnoty sloupce. Pokud jsou verze řádku „před“ a „po“ přesně stejná, nezpůsobí to, že se do protokolu transakcí zapíše jakákoli věc, jinak bude aktualizace zaznamenána.
  • Původní sloupec je označen jako vynechaný (jedná se pouze o změnu metadat v sys.sysrscols. rscolid aktualizováno na velké celé číslo a status bit 2 nastaveno na označené zrušeno)
  • Záznam v sys.sysrscols Pro nový sloupec je změněn tak, aby mu poskytl rscolid starého sloupce.

Operace, která má potenciál způsobit hodně protokolování, je UPDATE všech řádků v tabulce, což však neznamená, že to bude vždy ) nastat. Pokud jsou obrázky „před“ a „za“ v řádku stejné, bude to považováno za neaktualizovaná aktualizace a nebude dosud protokolováno z mého testování.

Vysvětlení, proč získáváte spoustu protokolování, bude záviset na tom, proč přesně verze „před“ a „po“ v řádku nejsou stejné.

U sloupců s proměnnou délkou uložených ve formátu FixedVar jsem zjistil, že nastavení na NOT NULL Vždy způsobí změnu v řádku, který je třeba zaznamenat. Počet sloupců a počet sloupců s proměnnou délkou se zvýší a nový sloupec se přidá na konec sekce s proměnnou délkou duplikováním dat.

datetimeoffset(0) má však pevnou délku a pro sloupce s pevnou délkou uložené ve formátu FixedVar se zdá, že starý i nový sloupec mají v datové části řádku s pevnou délkou stejný slot a jako oba mají stejnou délku a hodnotu verze „před“ a „po“ řádku jsou stejné . To lze vidět v odpovědi @ Aarona. Obě verze řádku před a za ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL; Jsou

0x10000c00 01000000 00000000 020000

Toto není zaznamenáno.

Logicky z mého popisu událostí by se řádek měl ve skutečnosti lišit, protože počet sloupců 02 By měl být zvýšen na 03, Ale v praxi k takové změně skutečně nedojde.

Některé možné důvody, proč k tomu může dojít ve sloupci s pevnou délkou, jsou

  • Pokud byl sloupec původně deklarován jako SPARSE, nový sloupec by byl uložen v jiné části řádku než původní, což způsobí, že obrázky před a za řádkem budou odlišné.
  • Pokud používáte některou z možností komprese, předchozí a následující verze řádku se budou lišit, protože se bude zvyšovat část počtu sloupců v poli CD.
  • U databází s jednou z povolených možností izolace snímku jsou aktualizovány informace o verzích v každém řádku (@SQL Kiwi poukazuje na to, že k tomu může dojít také v databázích bez aktivovaného SI jak je popsáno zde ).
  • Může existovat některá předchozí operace ALTER TABLE, Která byla implementována jako změna pouze metadat a dosud nebyla na řádek použita. Například pokud byl přidán nový sloupec s proměnnou délkou s nulovatelnou délkou, pak se původně použije jako změna pouze metadat a do řádků se zapíše pouze při další aktualizaci (zápis, ke kterému skutečně dojde v této poslední instanci, je pouze aktualizován na sekce počtu sloupců a NULL_BITMAP jako sloupec NULLvarchar na konci řádku nezabírá žádné místo)
32
Martin Smith

Stál jsem před stejným problémem ohledně stolu s 200 000 000 řádků. Zpočátku jsem přidal sloupec nullable, pak aktualizoval všechny řádky a nakonec změnil sloupec na NOT NULL přes ALTER TABLE ALTER COLUMN prohlášení. To mělo za následek neuvěřitelně velké vyfukování logfilu (170 GB růst).

Nejrychlejším způsobem, který jsem našel, bylo následující:

  1. Přidejte sloupec pomocí výchozí hodnoty

    ALTER TABLE table1 ADD column1 INT NOT NULL DEFAULT (1)
    
  2. Vypusťte výchozí omezení pomocí dynamického SQL, protože dříve nebylo pojmenováno:

    DECLARE 
        @constraint_name SYSNAME,
        @stmt NVARCHAR(510);
    
    SELECT @CONSTRAINT_NAME = DC.NAME
    FROM SYS.DEFAULT_CONSTRAINTS DC
    INNER JOIN SYS.COLUMNS C
        ON DC.PARENT_OBJECT_ID = C.OBJECT_ID
        AND DC.PARENT_COLUMN_ID = C.COLUMN_ID
    WHERE
        PARENT_OBJECT_ID = OBJECT_ID('table1')
        AND C.NAME = 'column1';
    

Doba provádění klesla z> 30 minut na 10 minut, včetně replikace změn pomocí transakční replikace. Používám instalaci serveru SQL Server 2008 (SP2).

5
Fritz

Provedl jsem následující test:

create table tblCheckResult(
        ColID   int identity
    ,   dtoDateTime Datetimeoffset(0) null
    )

 go

insert into tblCheckResult (dtoDateTime)
select getdate()
go 10000

checkpoint 

ALTER TABLE tblCheckResult 
ALTER COLUMN [dtoDateTime] [datetimeoffset](0) NOT NULL

select * from fn_dblog(null,null)

Věřím, že to má co do činění s vyhrazeným prostorem, který log obsahuje, pouze v případě, že transakci zrušíte. Podívejte se do funkce fn_dblog ve sloupci 'Log Reserve' pro řádek LOP_BEGIN_XACT a podívejte se, kolik místa se snaží rezervovat.

2
Keith Tate