[ Část 1 | Část 2 | Část 3 | Část 4 ]
V první části této série jsem ukázal, co se stane s fyzickou stránkou při změně sloupce IDENTITA z int na bigint. Aby to bylo jednoduché, vytvořil jsem velmi jednoduchou haldu bez indexů nebo omezení. Bohužel většina z nás tento druh luxusu nemá – důležitý stůl, který je třeba změnit, ale nelze jej jednoduše znovu vytvořit od nuly, má pravděpodobně několik atributů, které nám stojí přímo v cestě. V tomto příspěvku jsem chtěl ukázat ty běžnější, aniž bych se pouštěl do exotických věcí, jako je In-Memory OLTP a Columnstore.
Primární klíč
Doufejme, že všechny vaše tabulky mají primární klíč; pokud se však jedná o sloupec IDENTITY, nebude tak snadné změnit základní datový typ. Vezměte si tyto jednoduché příklady, klastrované i neklastrované primární klíče:
CREATE TABLE dbo.Test1 ( ID INT IDENTITY(1,1), CONSTRAINT PK_1 PRIMARY KEY NONCLUSTERED (ID) ); CREATE TABLE dbo.Test2 ( ID INT IDENTITY(1,1), CONSTRAINT PK_2 PRIMARY KEY CLUSTERED (ID) );
Pokud se pokusím změnit sloupec:
ALTER TABLE dbo.Test1 ALTER COLUMN ID BIGINT; GO ALTER TABLE dbo.Test2 ALTER COLUMN ID BIGINT;
Pro každý ALTER dostávám pár chybových zpráv (zobrazuje se pouze první pár):
Zpráva 5074, úroveň 16, stav 1Objekt 'PK_1' je závislý na sloupci 'ID'.
Zpráva 4922, úroveň 16, stav 9
ALTER TABLE ALTER COLUMN ID selhal, protože jeden nebo do tohoto sloupce má přístup více objektů.
Shrnutí:Budeme muset odstranit primární klíč , ať už je nebo není, je seskupený.
Indexy
Nejprve si vezměme několik tabulek jako výše a pomocí jedinečného indexu namísto primárního klíče:
CREATE TABLE dbo.Test3 ( ID INT IDENTITY(1,1), INDEX IX_3 UNIQUE NONCLUSTERED (ID) ); CREATE TABLE dbo.Test4 ( ID INT IDENTITY(1,1), INDEX IX_4 UNIQUE CLUSTERED (ID) );
Spuštění podobných příkazů ALTER výše vede ke stejným chybovým zprávám. To platí, i když zakážu indexy:
ALTER INDEX IX_3 ON dbo.Test3 DISABLE; GO ALTER INDEX IX_4 ON dbo.Test4 DISABLE;
Podobné výsledky pro různé další typy kombinací indexů, jako je zahrnutý sloupec nebo filtr:
CREATE TABLE dbo.Test5 ( ID INT IDENTITY(1,1), x CHAR(1) ); CREATE INDEX IX_5 ON dbo.Test5(x) INCLUDE(ID); CREATE TABLE dbo.Test6 ( ID INT IDENTITY(1,1), x CHAR(1) ); CREATE INDEX IX_6 ON dbo.Test6(x) WHERE ID > 0;
Shrnutí:Budeme muset zrušit a znovu vytvořit všechny indexy , seskupené nebo ne, které odkazují na sloupec IDENTITY – v klíči nebo INCLUDE. Pokud je sloupec IDENTITY součástí seskupeného indexu, znamená to všechny indexy , protože všechny budou podle definice odkazovat na shlukovací klíč. A jejich deaktivace nestačí.
Vypočítané sloupce
I když by to mělo být relativně vzácné, viděl jsem vypočítané sloupce založené na sloupci IDENTITA. Například:
CREATE TABLE dbo.Test7 ( ID INT IDENTITY(1,1), NextID AS (ID + 1) );
Tentokrát, když se pokusíme změnit, dostaneme stejný pár chyb, ale s mírně odlišným textem:
Zpráva 5074, Úroveň 16, Stav 1Sloupec 'NextID' je závislý na sloupci 'ID'.
Zpráva 4922, Úroveň 16, Stav 9
ALTER TABLE ALTER COLUMN ID selhal, protože jeden nebo do tohoto sloupce má přístup více objektů.
To platí i v případě, že změníme definici vypočítaného sloupce tak, aby odpovídala cílovému datovému typu:
CREATE TABLE dbo.Test8 ( ID INT IDENTITY(1,1), NextID AS (CONVERT(BIGINT, ID) + 1) );
Shrnutí:Budeme muset změnit definice počítaných sloupců nebo je úplně vypustit.
Indexovaná zobrazení
Indexovaná zobrazení také vidí svůj spravedlivý podíl na využití. Pojďme vytvořit indexovaný pohled, který ani neodkazuje na sloupec IDENTITY (všimněte si, že v základní tabulce nejsou žádné další indexy nebo omezení):
CREATE TABLE dbo.Test9 ( ID INT IDENTITY(1,1), x CHAR(1) ); GO CREATE VIEW dbo.vTest9A WITH SCHEMABINDING AS SELECT x, c = COUNT_BIG(*) FROM dbo.Test9 GROUP BY x; GO CREATE UNIQUE CLUSTERED INDEX IX_9A ON dbo.vTest9A(x);
Ještě jednou zkusíme ALTER a tentokrát uspěje . Přiznám se, že mě to překvapilo, protože SCHEMABINDING má zabránit jakýmkoli změnám v podkladové tabulce, ale v tomto případě se vztahuje pouze na sloupce, na které se v pohledu výslovně odkazuje. Pokud vytvoříme trochu jiný pohled:
CREATE VIEW dbo.vTest9B WITH SCHEMABINDING AS SELECT ID, c = COUNT_BIG(*) FROM dbo.Test9 GROUP BY ID; GO CREATE UNIQUE CLUSTERED INDEX IX_9B ON dbo.vTest9B(ID);
Nyní selžeme kvůli závislosti sloupce:
Zpráva 5074, Úroveň 16, Stav 1Objekt 'vTest9B' je závislý na sloupci 'ID'.
Zpráva 4922, Úroveň 16, Stav 9
ALTER TABLE ALTER COLUMN ID selhal, protože jeden nebo do tohoto sloupce má přístup více objektů.
Shrnutí:Budeme muset zrušit všechny indexy všech zobrazení, která explicitně odkazují na sloupec IDENTITY , stejně jako všechny indexy na libovolném pohledu, který odkazuje na sloupec IDENTITY v jeho seskupeném indexu.
Příchozí cizí klíče
Pravděpodobně nejproblematičtějším aspektem primárních klíčů IDENTITY je to, že ze samotné podstaty náhradních klíčů je často cílem použít tento náhradní klíč ve více souvisejících tabulkách. Nechystám se teď obhajovat vyhýbání se referenční integritě, ale i zde nám to bude potenciálně stát trochu v cestě. Shora víme, že nemůžeme změnit sloupec, který je součástí primárního klíče nebo jedinečného omezení, a aby sem jiná tabulka ukazovala s omezením cizího klíče, musí existovat jedna z těchto dvou věcí. Řekněme, že máme následující dvě tabulky:
CREATE TABLE dbo.TestParent ( ID INT IDENTITY(1,1), CONSTRAINT PK_Parent PRIMARY KEY CLUSTERED(ID) ); GO CREATE TABLE dbo.TestChild ( ParentID INT NOT NULL, CONSTRAINT FK_Parent FOREIGN KEY(ParentID) REFERENCES dbo.TestParent(ID) );
Než vůbec budeme moci uvažovat o změně datového typu sloupce, musíme zrušit omezení:
ALTER TABLE dbo.TestParent DROP CONSTRAINT PK_Parent;
A samozřejmě nemůžeme, aniž bychom také zrušili omezení cizího klíče, protože to vede k následující chybové zprávě:
Msg 3725, Level 16, State 0Na omezení 'PK_Parent' odkazuje tabulka 'TestChild', omezení cizího klíče 'FK_Parent'.
Msg 3727, Level 16, State 0
Mohlo neupustit omezení. Viz předchozí chyby.
Tato chyba přetrvává, i když nejprve zakážeme omezení cizího klíče:
ALTER TABLE dbo.TestChild NOCHECK CONSTRAINT FK_Parent;
Kromě toho zvažte, že ke změně datového typu budete potřebovat také odkazující sloupce. A dále se tyto sloupce pravděpodobně účastní některých z výše uvedených prvků, které by mohly podobně zabránit změně v podřízených tabulkách. Chcete-li, aby byly věci zcela kopatické a synchronizované, budeme muset:
- zrušte příslušná omezení a indexy v nadřazené tabulce
- zrušte příslušná omezení cizího klíče na podřízené tabulky
- vypusťte všechny indexy z podřízených tabulek, které odkazují na sloupec FK (a zabývají se všemi relevantními vypočítanými sloupci / indexovanými zobrazeními)
- změňte typ dat v nadřazených a všech podřízených tabulkách
- vše znovu vytvořit
Shrnutí:Budeme muset zahodit příchozí cizí klíče a potenciálně to bude mít celou řadu kaskádových efektů. Pouhé zakázání cizích klíčů nestačí a stejně by to nebylo trvalé řešení, protože datový typ se nakonec bude muset změnit i v podřízených tabulkách.
Závěr
Vím, že se zdá, že postupujeme pomalu, a přiznávám, že v tomto příspěvku se zdá, že se spíše odkláním od řešení než k němu. Dostanu se tam, je tu jen spousta informací, které je třeba prezentovat jako první, včetně věcí, které tento typ změny ztěžují. Z výše uvedených shrnutí budeme muset:
- přetáhněte a znovu vytvořte relevantní indexy v hlavní tabulce
- změnit nebo vypustit vypočítané sloupce, které zahrnují sloupec IDENTITY
- upustit indexy u indexovaných zobrazení, která odkazují na sloupec IDENTITY
- zacházet s příchozími cizími klíči, které ukazují na sloupec IDENTITY
Bohužel, mnoho z těchto věcí je catch-22. Sloupec nelze změnit, protože na něm závisí index, a index nelze změnit, dokud se sloupec nezmění. Nebylo by skvělé, kdyby ALTER INDEX podporoval REBUILD WITH (ONLINE = ON, CHANGE_COLUMN (COLUMN = ID, NEW_TYPE = BIGINT))
? A CASCADE_CHANGE_TO_REFERENCING_KEYS,COLUMNS,INDEXES,VIEWS,ETC
? No, není (kontroloval jsem). Musíme tedy najít způsoby, jak tyto věci usnadnit. Zůstaňte naladěni na 3. část.
—
[ Část 1 | Část 2 | Část 3 | Část 4 ]