[ Del 1 | Del 2 | Del 3 | Del 4 ]
Indtil videre i denne serie har jeg demonstreret den direkte fysiske indvirkning på siden, når der skal opgraderes fra int
til bigint
, og gentog derefter flere af de almindelige blokkere til denne operation. I dette indlæg ville jeg undersøge to potentielle løsninger:en enkel og en utrolig indviklet.
Den nemme måde
Jeg blev frarøvet min torden en lille smule i en kommentar til mit tidligere indlæg – Keith Monroe foreslog, at du bare kunne flytte bordet til det nederste negativ bundet af heltalsdatatypen, hvilket fordobler din kapacitet for nye værdier. Du kan gøre dette med DBCC CHECKIDENT
:
DBCC CHECKIDENT(N'dbo.TableName', RESEED, -2147483648);
Dette kunne fungere, forudsat at surrogatværdierne ikke har betydning for slutbrugere (eller, hvis de gør, at brugere ikke bliver skræmt af pludselig at få negative tal). Jeg formoder, at du kunne narre dem med en udsigt:
CREATE VIEW dbo.ViewNameAS SELECT ID =CONVERT(bigint, CASE WHEN ID <0 THEN (2147483648*2) - 1 + CONVERT(bigint, ID) ELSE ID END) FROM dbo.TableName;
Det betyder, at den bruger, der tilføjede ID = -2147483648
ville faktisk se +2147483648
, brugeren, der tilføjede ID = -2147483647
ville se +2147483649
, og så videre. Du bliver dog nødt til at justere en anden kode for at være sikker på at foretage den omvendte beregning, når brugeren indtaster det ID
, f.eks.
ÆNDRINGSPROCEDURE dbo.GetRowByID @ID bigintASBEGIN SÆT NOCOUNT ON; DECLARE @RealID bigint; SET @RealID =CASE WHEN @ID> 2147483647 THEN @ID - (2147483648*2) + 1 ANDET @ID END; SELECT ID, @ID /*, other columns */ FROM dbo.TableName WHERE ID =@RealID;ENDGO
Jeg er ikke vild med denne sløring. Overhovedet. Det er rodet, vildledende og udsat for fejl. Og det tilskynder til at have synlighed i surrogatnøgler – generelt IDENTITY
værdier bør ikke eksponeres for slutbrugere, så de burde virkelig være ligeglade med, om de er kunde 24, 642, -376 eller meget større tal på begge sider af nul.
Denne "løsning" forudsætter også, at du ikke har kode nogen steder, der bestilles efter IDENTITY
kolonne for at præsentere de senest indsatte rækker først, eller udlede, at den højeste IDENTITY
værdien skal være den nyeste række. Kode, der gør stole på sorteringsrækkefølgen af IDENTITY
kolonne, enten eksplicit eller implicit (hvilket måske er mere, end du tror, hvis det er det klyngede indeks), vil ikke længere præsentere rækkerne i forventet rækkefølge – den vil vise alle rækkerne, der er oprettet efter RESEED
, startende med den første, og derefter vil den vise alle de rækker, der er oprettet før RESEED
, begyndende med den første.
Den primære fordel ved denne tilgang er, at den ikke kræver, at du ændrer datatypen, og som følge heraf er RESEED
ændring kræver ingen ændringer af indekser, begrænsninger eller indgående fremmednøgler.
Ulempen – udover kodeændringerne nævnt ovenfor, selvfølgelig – er, at dette kun køber dig tid på kort sigt. Til sidst vil du også udtømme alle de tilgængelige negative heltal. Og tro ikke, at dette fordobler brugstiden for den aktuelle version af tabellen med hensyn til tid – i mange tilfælde accelererer datavæksten og forbliver ikke konstant, så du vil bruge de næste 2 milliarder rækker meget hurtigere end de første 2 milliarder.
En sværere måde
En anden fremgangsmåde, du kan tage, er at stoppe med at bruge en IDENTITY
kolonne helt; i stedet kan du konvertere til at bruge en SEQUENCE
. Du kan oprette en ny bigint
kolonne, skal du indstille standardværdien til den næste værdi fra en SEQUENCE
, opdater alle disse værdier med værdierne fra den oprindelige kolonne (i batches, hvis det er nødvendigt), slip den oprindelige kolonne og omdøb den nye kolonne. Lad os oprette denne fiktive tabel og indsætte en enkelt række:
CREATE TABLE dbo.SequenceDemo( ID int IDENTITY(1,1), x char(1), CONSTRAINT PK_SD_Identity PRIMÆR NØGLE KLUSTERET (ID));GO INSERT dbo.SequenceDemo(x) VALUES('x');
Dernæst opretter vi en SEQUENCE
der starter lige ud over den øvre grænse af en int:
OPRET SEKVENS dbo.BeyondIntAS bigintSTART MED 2147483648 FORØG MED 1;
Dernæst de ændringer i tabellen, der er nødvendige for at skifte til at bruge SEQUENCE
for den nye kolonne:
BEGIN TRANSAKTIONEN; -- tilføj en ny "identitet"-kolonne:ÆNDRINGSTABEL dbo.SequenceDemo ADD ID2 bigint;GO -- sæt den nye kolonne lig med de eksisterende identitetsværdier-- for store tabeller skal du muligvis gøre dette i batches:OPDATERING dbo.SequenceDemo SET ID2 =ID; -- gør det nu ikke nullbart og tilføj standarden fra vores SEQUENCE:ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 bigint NOT NULL;ALTER TABEL dbo.SequenceDemo TILFØJ BEGRÆNSNING DF_SD_Identity DEFAULT NÆSTE VÆRDI FOR dbo.BeyondInt FOR ID2; -- nødt til at droppe den eksisterende PK (og eventuelle indekser):ÆNDRINGSTABEL dbo.SequenceDemo DROP CONSTRAINT PK_SD_Identity; -- slip den gamle kolonne og omdøb den nye:ALTER TABLE dbo.SequenceDemo DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN'; -- sæt nu PK'en op igen:ÆNDRINGSTABEL dbo.SequenceDemo TILFØJ BEGRÆNSNING PK_SD_Identity PRIMÆR NØGLE KLYNGET (ID); FORBAGE TRANSAKTION;
I dette tilfælde vil den næste indsættelse give følgende resultater (bemærk at SCOPE_IDENTITY()
returnerer ikke længere en gyldig værdi):
INSERT dbo.SequenceDemo(x) VALUES('y');SELECT Si =SCOPE_IDENTITY();SELECT ID, x FROM dbo.SequenceDemo; /* resultater Si----NULL ID x---------- -1 x2147483648 y */
Hvis tabellen er stor, og du skal opdatere den nye kolonne i batches i stedet for ovenstående one-shot transaktion, som jeg har beskrevet her – så brugerne kan interagere med tabellen i mellemtiden – skal du have en trigger på plads for at tilsidesætte SEQUENCE
værdi for eventuelle nye rækker, der er indsat, så de fortsat matcher det, der udlæses til enhver kaldekode. (Dette forudsætter også, at du stadig har lidt plads i heltalområdet til at fortsætte med at acceptere nogle opdateringer; ellers, hvis du allerede har opbrugt rækkevidden, bliver du nødt til at tage lidt nedetid – eller bruge den nemme løsning ovenfor på kort sigt .)
Lad os droppe alt og starte forfra, så tilføjer vi bare den nye kolonne:
DROP TABLE dbo.SequenceDemo;DROP SEQUENCE dbo.BeyondInt;GO CREATE TABLE dbo.SequenceDemo( ID int IDENTITY(1,1), x char(1), CONSTRAINT PK_SD_Identity PRIMÆR NØGLE CLUSTERED (ID));GO INSERT dbo .SequenceDemo(x) VALUES('x');GO CREATE SEQUENCE dbo.BeyondIntAS bigintSTART MED 2147483648 FORØG MED 1;GO ÆNDRINGSTABEL dbo.SequenceDemo ADD ID2 bigint;GO
Og her er udløseren, vi tilføjer:
OPRET TRIGGER dbo.After_SequenceDemoON dbo.SequenceDemoAFTER INSERTASBEGIN UPDATE sd SET sd.ID2 =sd.ID FRA dbo.SequenceDemo AS sd INNER JOIN indsat AS i ON sd.ID =i.ID;END
Denne gang vil den næste indsættelse fortsætte med at generere rækker i det nederste område af heltal for begge kolonner, indtil alle de allerede eksisterende værdier er blevet opdateret, og resten af ændringerne er blevet foretaget:
INSERT dbo.SequenceDemo(x) VALUES('y');SELECT Si =SCOPE_IDENTITY();SELECT ID, ID2, x FROM dbo.SequenceDemo; /* resultater Si----2 ID ID2 x---- ---- --1 NULL x2 2 y */
Nu kan vi fortsætte med at opdatere den eksisterende ID2
værdier, mens nye rækker fortsætter med at blive indsat inden for det nedre område:
INDSTIL ANTAL TIL; DEKLARE @r INT =1; MENS @r> 0BEGIN START TRANSAKTION; OPDATERING TOP (10000) dbo.SequenceDemo SET ID2 =ID HVOR ID2 ER NULL; SET @r =@@ RÆKEL; FORBAGE TRANSAKTION; -- CHECKPOINT; -- hvis det er enkelt -- BACKUP LOG ... -- hvis fuldEND
Når vi har opdateret alle de eksisterende rækker, kan vi fortsætte med resten af ændringerne og derefter slippe triggeren:
BEGIN TRANSACTION;ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 BIGINT NOT NULL;ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity STANDARD NÆSTE VÆRDI FOR dbo.BeyondInt FOR ID2;ALTER TABLE dbo.SequenceDemo TABLE dbo.SequenceDequence; DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN';ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT PK_SD_Identity PRIMÆR NØGLE CLUSTERED (ID);DROP TRIGGER dbo.InsteadACTIONmof;Nu vil den næste indsættelse generere disse værdier:
INSERT dbo.SequenceDemo(x) VALUES('z');SELECT Si =SCOPE_IDENTITY();SELECT ID, x FROM dbo.SequenceDemo; /* resultater Si----NULL ID x---------- -1 x2 y2147483648 z */Hvis du har kode, der er afhængig af
SCOPE_IDENTITY()
,@@IDENTITY
, ellerIDENT_CURRENT()
, ville det også skulle ændres, da disse værdier ikke længere udfyldes efter en indsættelse – selvomOUTPUT
klausulen skal fortsætte med at fungere korrekt i de fleste scenarier. Hvis du har brug for din kode for at fortsætte med at tro, genererer tabellen enIDENTITY
værdi, så kan du bruge en trigger til at forfalske dette – men det ville kun være i stand til at udfylde@@IDENTITY
ved indsættelse, ikkeSCOPE_IDENTITY()
. Dette kan stadig kræve ændringer, fordi du i de fleste tilfælde ikke ønsker at stole på@@IDENTITY
for noget (så hvis du vil foretage ændringer, skal du fjerne alle antagelser om enIDENTITY
kolonne overhovedet).CREATE TRIGGER dbo.FakeIdentityON dbo.SequenceDemoINSTEAD OF INSERTASBEGIN SET NOCOUNT ON; DECLARE @lowestID bigint =(SELECT MIN(id) FROM indsat); DECLARE @sql nvarchar(max) =N'DECLARE @foo TABLE(ID bigint IDENTITY(' + CONVERT(varchar(32), @lowestID) + N',1));'; VÆLG @sql +=N'INSERT @foo STANDARDVÆRDIER;' FRA indsat; EXEC sys.sp_executesql @sql; INSERT dbo.SequenceDemo(ID, x) SELECT ID, x FROM indsat;ENDNu vil den næste indsættelse generere disse værdier:
INSERT dbo.SequenceDemo(x) VALUES('a');SELECT Si =SCOPE_IDENTITY(), Ident =@@IDENTITY;SELECT ID, x FROM dbo.SequenceDemo; /* resultater Si Ident---- -----NULL 2147483649 ID x---------- -1 x2 y2147483648 z2147483649 a */Med denne løsning vil du stadig skulle håndtere andre begrænsninger, indekser og tabeller med indgående fremmednøgler. Lokale begrænsninger og indekser er ret ligetil, men jeg vil behandle den mere komplekse situation med fremmednøgler i den næste del af denne serie.
En, der ikke virker, men jeg ville ønske, den ville
ALTER TABLE SWITCH
kan være en meget effektiv måde at lave nogle metadataændringer på, som ellers er svære at opnå. Og i modsætning til hvad mange tror, involverer dette ikke kun partitionering og er ikke begrænset til Enterprise Edition. Følgende kode fungerer på Express, og er en metode, folk har brugt til at tilføje eller fjerneIDENTITY
ejendom på et bord (igen, uden at tage højde for fremmednøgler og alle de andre irriterende blokkere).CREATE TABLE dbo.WithIdentity( ID int IDENTITY(1,1) NOT NULL); CREATE TABLE dbo.WithoutIdentity( ID int NOT NULL); ALTER TABLE dbo.WithIdentity SKIFT TIL dbo.WithoutIdentity;GO DROP TABLE dbo.WithIdentity;EXEC sys.sp_rename N'dbo.WithoutIdentity', N'dbo.WithIdentity', 'OBJECT';Dette virker, fordi datatyperne og nullbarheden matcher nøjagtigt, og der er ikke taget hensyn til
IDENTITY
attribut. Prøv dog at blande datatyper, og tingene fungerer ikke så godt:CREATE TABLE dbo.SourceTable( ID int IDENTITY(1,1) NOT NULL); CREATE TABLE dbo.TrySwitch( ID bigint IDENTITY(1,1) NOT NULL); ÆNDRINGSTABEL dbo.SourceTable SKIFTER TIL dbo.TrySwitch;Dette resulterer i:
Msg 4944, niveau 16, tilstand 1
ALTER TABLE SWITCH-sætning mislykkedes, fordi kolonne 'ID' har datatypen int i kildetabellen 'dbo.SourceTable', som er forskellig fra dens type bigint i måltabellen 'dbo.TrySwitch'.Det ville være fantastisk, hvis en
SWITCH
operation kunne bruges i et scenarie som dette, hvor den eneste forskel i skemaet faktisk ikke *krævede* nogen fysiske ændringer for at imødekomme (igen, som jeg viste i del 1, bliver data omskrevet til nye sider, selvom det er ikke nødvendigt at gøre det).Konklusion
Dette indlæg undersøgte to potentielle løsninger til enten at købe dig tid, før du ændrer din eksisterende
IDENTITY
kolonne eller opgiveIDENTITY
helt lige nu til fordel for enSEQUENCE
. Hvis ingen af disse løsninger er acceptable for dig, så se venligst del 4, hvor vi vil løse dette problem direkte.—
[ Del 1 | Del 2 | Del 3 | Del 4 ]