[ Del 1 | Del 2 | Del 3 | Del 4 ]
I den första delen av den här serien visade jag vad som händer med en fysisk sida när man ändrar en IDENTITY-kolumn från en int till en bigint. För att göra det enkelt skapade jag en mycket enkel hög utan index eller begränsningar. Tyvärr har de flesta av oss inte den typen av lyx – ett viktigt bord som behöver förändras men som inte helt enkelt kan återskapas från grunden har troligen flera attribut som står direkt i vägen för oss. I det här inlägget ville jag visa de vanligare, utan att ens gå in på exotiska saker som In-Memory OLTP och Columnstore.
Primär nyckel
Förhoppningsvis har alla dina tabeller en primärnyckel; Om kolumnen IDENTITY är inblandad kommer det dock inte att vara så lätt att ändra den underliggande datatypen. Ta dessa enkla exempel, både klustrade och icke-klustrade primärnycklar:
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) );
Om jag försöker ändra kolumnen:
ALTER TABLE dbo.Test1 ALTER COLUMN ID BIGINT; GO ALTER TABLE dbo.Test2 ALTER COLUMN ID BIGINT;
Jag får ett par felmeddelanden för varje ALTER (visar bara det första paret):
Msg 5074, Level 16, State 1Objektet 'PK_1' är beroende av kolumn 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID misslyckades eftersom en eller fler objekt kommer åt den här kolumnen.
Sammanfattning:Vi måste ta bort primärnyckeln , om det är eller inte är klustrade.
Index
Låt oss först ta ett par tabeller som ovan och använda ett unikt index istället för en primärnyckel:
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) );
Att köra liknande ALTER-kommandon ovan leder till samma felmeddelanden. Detta förblir sant även om jag inaktiverar indexen:
ALTER INDEX IX_3 ON dbo.Test3 DISABLE; GO ALTER INDEX IX_4 ON dbo.Test4 DISABLE;
Liknande resultat för olika andra typer av indexkombinationer, till exempel en inkluderad kolumn eller ett filter:
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;
Sammanfattning:Vi kommer att behöva ta bort och återskapa eventuella index , klustrade eller inte, som refererar till IDENTITY-kolumnen – i nyckeln eller INCLUDE. Om kolumnen IDENTITY är en del av det klustrade indexet betyder det alla index , eftersom de alla kommer att referera till klustringsnyckeln per definition. Och det räcker inte att inaktivera dem.
beräknade kolumner
Även om detta borde vara relativt sällsynt, har jag sett beräknade kolumner baserade på IDENTITY-kolumnen. Till exempel:
CREATE TABLE dbo.Test7 ( ID INT IDENTITY(1,1), NextID AS (ID + 1) );
Den här gången, när vi försöker ändra, får vi samma felpar, men med lite annorlunda text:
Msg 5074, Level 16, State 1Kolumnen 'NextID' är beroende av kolumn 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID misslyckades eftersom en eller fler objekt kommer åt den här kolumnen.
Detta gäller även om vi ändrar den beräknade kolumndefinitionen så att den matchar måldatatypen:
CREATE TABLE dbo.Test8 ( ID INT IDENTITY(1,1), NextID AS (CONVERT(BIGINT, ID) + 1) );
Sammanfattning:Vi kommer att behöva ändra definitionerna av beräknade kolumner, eller ta bort dem helt.
Indexerade vyer
Indexerade vyer ser också sin beskärda andel av användningen. Låt oss konstruera en indexerad vy som inte ens refererar till IDENTITY-kolumnen (observera inga andra index eller begränsningar på bastabellen):
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);
Än en gång kommer vi att prova ALTER, och den här gången lyckas det . Jag ska erkänna att jag blev förvånad över detta, eftersom SCHEMABINDING är tänkt att förhindra några ändringar i den underliggande tabellen, men i det här fallet gäller det bara för kolumner som uttryckligen hänvisas till i vyn. Om vi skapar en lite annorlunda vy:
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);
Nu kommer vi att misslyckas på grund av kolumnberoendet:
Msg 5074, Level 16, State 1Objektet 'vTest9B' är beroende av kolumn 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID misslyckades eftersom en eller fler objekt kommer åt den här kolumnen.
Sammanfattning:Vi kommer att behöva ta bort alla index på alla vyer som uttryckligen refererar till IDENTITY-kolumnen , samt alla index på alla vyer som refererar till IDENTITY-kolumnen i dess klustrade index.
Inkommande främmande nycklar
Den förmodligen mest problematiska aspekten av IDENTITY-primärnycklar är att genom själva naturen hos surrogat är hela poängen ofta att använda denna surrogatnyckel i flera relaterade tabeller. Nu tänker jag inte förespråka att man undviker referensintegritet, men det kommer potentiellt att stå i vägen lite här också. Vi vet från ovan att vi inte kan ändra en kolumn som är en del av en primärnyckel eller unik begränsning, och för att en annan tabell ska peka här med en främmande nyckel-begränsning måste en av dessa två saker existera. Så låt oss säga att vi har följande två tabeller:
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) );
Innan vi ens kan överväga att ändra kolumnens datatyp måste vi släppa begränsningen:
ALTER TABLE dbo.TestParent DROP CONSTRAINT PK_Parent;
Och naturligtvis kan vi inte, utan att också släppa begränsningen för främmande nyckel, eftersom detta ger följande felmeddelande:
Msg 3725, Level 16, State 0Begränsningen 'PK_Parent' refereras av tabellen 'TestChild', främmande nyckel-begränsning 'FK_Parent'.
Msg 3727, Level 16, State 0
Kunde inte släppa begränsningen. Se tidigare fel.
Det här felet kvarstår även om vi först inaktiverar den främmande nyckeln:
ALTER TABLE dbo.TestChild NOCHECK CONSTRAINT FK_Parent;
Utöver detta, tänk på att du också behöver referenskolumnerna för att ändra deras datatyp. Och vidare, dessa kolumner deltar sannolikt i några av ovanstående element som på liknande sätt kan förhindra ändringen på de underordnade tabellerna. För att få saker helt samspelta och synkroniserade måste vi:
- släpp relevanta begränsningar och index på den överordnade tabellen
- släpp de relevanta begränsningarna för främmande nyckel på de underordnade tabellerna
- släpp alla index på underordnade tabeller som refererar till FK-kolumnen (och hantera alla relevanta beräknade kolumner/indexerade vyer)
- ändra datatypen på överordnade och alla underordnade tabeller
- återskapa allt
Sammanfattning:Vi kommer att behöva ta bort inkommande främmande nycklar och, potentiellt, kommer detta att ha en hel uppsjö av kaskadeffekter. Att helt enkelt inaktivera de främmande nycklarna är inte tillräckligt, och skulle inte vara en permanent lösning i alla fall, eftersom datatypen kommer att behöva ändras i de underordnade tabellerna så småningom också.
Slutsats
Jag vet att det verkar som om vi går långsamt, och jag medger att jag i det här inlägget verkar dra mig ifrån en lösning snarare än mot en. Jag kommer dit, det finns bara mycket information att presentera först, inklusive de saker som gör den här typen av förändring svår. Skrapat från sammanfattningarna ovan kommer vi att behöva:
- släpp och återskapa relevanta index i huvudtabellen
- ändra eller ta bort beräknade kolumner som involverar IDENTITY-kolumnen
- släpp index på indexerade vyer som refererar till IDENTITY-kolumnen
- hantera inkommande främmande nycklar som pekar på IDENTITY-kolumnen
Tyvärr är många av dessa saker catch-22. Du kan inte ändra en kolumn eftersom ett index är beroende av det, och du kan inte ändra indexet förrän kolumnen har ändrats. Skulle det inte vara bra om ALTER INDEX stödde REBUILD WITH (ONLINE = ON, CHANGE_COLUMN (COLUMN = ID, NEW_TYPE = BIGINT))
? Och CASCADE_CHANGE_TO_REFERENCING_KEYS,COLUMNS,INDEXES,VIEWS,ETC
? Tja, det gör det inte (jag kollade). Så vi måste hitta sätt att göra dessa saker enklare. Håll utkik efter del 3.
—
[ Del 1 | Del 2 | Del 3 | Del 4 ]