[ Del 1 | Del 2 | Del 3 | Del 4 ]
I den første del af denne serie viste jeg, hvad der sker med en fysisk side, når man ændrer en IDENTITY-kolonne fra en int til en bigint. For at holde tingene enkle oprettede jeg en meget simpel bunke uden indekser eller begrænsninger. Desværre har de fleste af os ikke den slags luksus – et vigtigt bord, der skal ændres, men som ikke bare kan genskabes fra bunden, har sandsynligvis flere egenskaber, der står direkte i vejen for os. I dette indlæg ville jeg vise de mere almindelige, uden overhovedet at komme ind på eksotiske ting som In-Memory OLTP og Columnstore.
Primær nøgle
Forhåbentlig har alle dine tabeller en primær nøgle; hvis IDENTITY-kolonnen er involveret, vil det dog ikke være så let at ændre den underliggende datatype. Tag disse enkle eksempler, både klyngede og ikke-klyngede primærnøgler:
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) );
Hvis jeg prøver at ændre kolonnen:
ALTER TABLE dbo.Test1 ALTER COLUMN ID BIGINT; GO ALTER TABLE dbo.Test2 ALTER COLUMN ID BIGINT;
Jeg får et par fejlmeddelelser for hver ALTER (viser bare det første par):
Meddelelse 5074, niveau 16, tilstand 1Objektet 'PK_1' er afhængig af kolonne 'ID'.
Besked 4922, niveau 16, tilstand 9
ALTER TABLE ALTER COLUMN ID mislykkedes, fordi en eller flere objekter får adgang til denne kolonne.
Oversigt:Vi bliver nødt til at slippe den primære nøgle , om det er eller ej er grupperet.
Indekser
Lad os først tage et par tabeller som ovenfor, og bruge et unikt indeks i stedet for en primær nøgle:
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) );
At køre lignende ALTER-kommandoer ovenfor, fører til de samme fejlmeddelelser. Dette forbliver sandt, selvom jeg deaktiverer indekserne:
ALTER INDEX IX_3 ON dbo.Test3 DISABLE; GO ALTER INDEX IX_4 ON dbo.Test4 DISABLE;
Lignende resultater for forskellige andre typer indekskombinationer, såsom en inkluderet kolonne eller et 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;
Oversigt:Vi bliver nødt til at droppe og genskabe eventuelle indekser , grupperet eller ej, der refererer til IDENTITY-kolonnen – i nøglen eller INCLUDE. Hvis IDENTITY-kolonnen er en del af det klyngede indeks, betyder det alle indekser , da de alle vil referere til klyngingsnøglen per definition. Og det er ikke nok at deaktivere dem.
Beregnet kolonner
Selvom dette burde være relativt sjældent, har jeg set beregnede kolonner baseret på IDENTITY-kolonnen. For eksempel:
CREATE TABLE dbo.Test7 ( ID INT IDENTITY(1,1), NextID AS (ID + 1) );
Denne gang, når vi forsøger at ændre, får vi det samme par fejl, men med lidt anderledes tekst:
Msg 5074, Level 16, State 1Kolonnen 'NextID' er afhængig af kolonne 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID mislykkedes, fordi en eller flere objekter får adgang til denne kolonne.
Dette er endda sandt, hvis vi ændrer den beregnede kolonnedefinition, så den matcher måldatatypen:
CREATE TABLE dbo.Test8 ( ID INT IDENTITY(1,1), NextID AS (CONVERT(BIGINT, ID) + 1) );
Sammendrag:Vi bliver nødt til at ændre definitionerne af beregnede kolonner eller helt droppe dem.
Indekserede visninger
Indekserede visninger ser også deres rimelige andel af brugen. Lad os konstruere en indekseret visning, der ikke engang refererer til IDENTITY-kolonnen (bemærk ingen andre indekser eller begrænsninger på basistabellen):
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);
Endnu en gang prøver vi ALTER, og denne gang lykkes det . Jeg skal indrømme, at jeg var overrasket over dette, da SCHEMABINDING formodes at forhindre ændringer i den underliggende tabel, men i dette tilfælde gælder det kun for kolonner, der eksplicit refereres til i visningen. Hvis vi skaber et lidt anderledes syn:
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 vil vi fejle på grund af kolonneafhængigheden:
Msg 5074, Level 16, State 1Objektet 'vTest9B' er afhængig af kolonnen 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID mislykkedes, fordi en eller flere objekter får adgang til denne kolonne.
Oversigt:Vi bliver nødt til at slette alle indekser på alle visninger, der eksplicit refererer til IDENTITY-kolonnen , samt alle indekser på enhver visning, der refererer til IDENTITY-kolonnen i dens klyngede indeks.
Indgående udenlandske nøgler
Sandsynligvis det mest problematiske aspekt af IDENTITY primære nøgler er, at i sagens natur er hele pointen ofte at bruge denne surrogatnøgle i flere relaterede tabeller. Nu vil jeg ikke gå ind for at undgå referentiel integritet, men det vil potentielt også stå i vejen for os her. Vi ved fra oven, at vi ikke kan ændre en kolonne, der er en del af en primær nøgle eller unik begrænsning, og for at en anden tabel kan pege her med en fremmed nøgle begrænsning, skal en af disse to ting eksistere. Så lad os sige, at vi har følgende to 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) );
Før vi overhovedet kan overveje at ændre kolonnens datatype, er vi nødt til at droppe begrænsningen:
ALTER TABLE dbo.TestParent DROP CONSTRAINT PK_Parent;
Og selvfølgelig kan vi ikke, uden også at droppe den fremmede nøgle-begrænsning, fordi dette giver følgende fejlmeddelelse:
Msg 3725, Level 16, State 0Begrænsningen 'PK_Parent' refereres til af tabellen 'TestChild', fremmednøgle begrænsning 'FK_Parent'.
Msg 3727, Level 16, State 0
Kunne ikke slippe begrænsning. Se tidligere fejl.
Denne fejl forbliver, selvom vi først deaktiverer den fremmede nøgle-begrænsning:
ALTER TABLE dbo.TestChild NOCHECK CONSTRAINT FK_Parent;
Oven i dette skal du overveje, at du også skal bruge referencekolonnerne for at ændre deres datatype. Og yderligere deltager disse kolonner sandsynligvis i nogle af ovenstående elementer, der på samme måde kan forhindre ændringen på de underordnede tabeller. For at få tingene helt kopacetiske og synkroniserede, bliver vi nødt til at:
- slip de relevante begrænsninger og indekser på den overordnede tabel
- slip de relevante fremmednøglebegrænsninger på de underordnede tabeller
- slip alle indekser på underordnede tabeller, der refererer til FK-kolonnen (og behandler eventuelle relevante beregnede kolonner/indekserede visninger)
- ændre datatypen på overordnede og alle underordnede tabeller
- genopret alt
Oversigt:Vi bliver nødt til at droppe indgående fremmednøgler og potentielt vil dette have en hel række af kaskadeeffekter. Blot at deaktivere fremmednøglerne er ikke nok, og det ville alligevel ikke være en permanent løsning, fordi datatypen også skal ændres i de underordnede tabeller.
Konklusion
Jeg ved, at det ser ud til, at vi bevæger os langsomt, og jeg indrømmer, at jeg i dette indlæg ser ud til at trække mig væk fra en løsning i stedet for mod én. Jeg kommer dertil, der er bare en masse information, der skal præsenteres først, inklusive de ting, der gør denne type forandring svær. Ud fra oversigterne ovenfor skal vi:
- slip og genskab relevante indekser på hovedtabellen
- ændre eller slette beregnede kolonner, der involverer IDENTITY-kolonnen
- slip indekser på indekserede visninger, der refererer til IDENTITY-kolonnen
- håndtere indgående fremmednøgler, der peger på IDENTITY-kolonnen
Desværre er mange af disse ting catch-22. Du kan ikke ændre en kolonne, fordi et indeks er afhængig af det, og du kan ikke ændre indekset, før kolonnen er ændret. Ville det ikke være fantastisk, hvis ALTER INDEX understøttede REBUILD WITH (ONLINE = ON, CHANGE_COLUMN (COLUMN = ID, NEW_TYPE = BIGINT))
? Og CASCADE_CHANGE_TO_REFERENCING_KEYS,COLUMNS,INDEXES,VIEWS,ETC
? Nå, det gør det ikke (jeg tjekkede). Så vi er nødt til at finde måder at gøre disse ting nemmere. Hold øje med del 3.
—
[ Del 1 | Del 2 | Del 3 | Del 4 ]