sql >> Databasteknik >  >> RDS >> Database

Minimera effekten av att bredda en IDENTITY-kolumn – del 3

[ Del 1 | Del 2 | Del 3 | Del 4 ]

Hittills i den här serien har jag visat den direkta fysiska påverkan på sidan när man ändrar storlek från int till bigint , och upprepade sedan flera av de vanliga blockerarna till denna operation. I det här inlägget ville jag undersöka två möjliga lösningar:en enkel och en otroligt invecklad.

Det enkla sättet

Jag blev berövad på min åska lite i en kommentar till mitt tidigare inlägg – Keith Monroe föreslog att du bara kunde flytta tabellen till den lägre negativ gräns för heltalsdatatypen, vilket fördubblar din kapacitet för nya värden. Du kan göra detta med DBCC CHECKIDENT :

DBCC CHECKIDENT(N'dbo.TableName', RESEED, -2147483648);

Detta kan fungera, förutsatt att surrogatvärdena inte har betydelse för slutanvändare (eller, om de gör det, att användare inte kommer att bli skrämda av att plötsligt få negativa siffror). Jag antar att du kan lura dem med en utsikt:

SKAPA VY dbo.ViewNameAS SELECT ID =CONVERT(bigint, CASE WHEN ID <0 THEN (2147483648*2) - 1 + CONVERT(bigint, ID) ELSE ID END) FROM dbo.TableName;

Detta betyder att användaren som lade till ID = -2147483648 skulle faktiskt se +2147483648 , användaren som lade till ID = -2147483647 skulle se +2147483649 , och så vidare. Du måste dock justera annan kod för att vara säker på att göra den omvända beräkningen när användaren anger det ID , t.ex.

ÄNDRA PROCEDUR dbo.GetRowByID @ID bigintASBEGIN STÄLL IN NOCOUNT ON; DECLARE @RealID bigint; SET @RealID =CASE WHEN @ID> 2147483647 THEN @ID - (2147483648*2) + 1 ANNAT @ID END; VÄLJ ID, @ID /*, andra kolumner */ FROM dbo.TableName WHERE ID =@RealID;ENDGO

Jag är inte galen i den här förvirringen. Alls. Det är rörigt, vilseledande och felbenäget. Och det uppmuntrar till insyn i surrogatnycklar – i allmänhet IDENTITY värden bör inte exponeras för slutanvändare, så de borde verkligen inte bry sig om de är kund 24, 642, -376 eller mycket större siffror på vardera sidan av noll.

Denna "lösning" förutsätter också att du inte har kod någonstans som sorterar efter IDENTITY kolumnen för att presentera de senast infogade raderna först, eller dra slutsatsen att den högsta IDENTITY värdet måste vara den senaste raden. Kod som gör lita på sorteringsordningen för IDENTITY kolumn, antingen explicit eller implicit (vilket kan vara mer än du tror om det är det klustrade indexet), kommer inte längre att presentera raderna i förväntad ordning – den kommer att visa alla rader som skapats efter RESEED , börjar med den första, och sedan visar den alla rader som skapats före RESEED , börjar med den första.

Den främsta fördelen med det här tillvägagångssättet är att det inte kräver att du ändrar datatypen, och som ett resultat, RESEED ändring kräver inga ändringar av index, begränsningar eller inkommande främmande nycklar.

Nackdelen – förutom kodändringarna som nämns ovan, förstås – är att detta bara köper dig tid på kort sikt. Så småningom kommer du att uttömma alla tillgängliga negativa heltal också. Och tro inte att detta fördubblar livslängden för den aktuella versionen av tabellen i termer av tid – i många fall accelererar datatillväxten och förblir inte konstant, så du kommer att använda upp de kommande 2 miljarderna raderna mycket snabbare än de första 2 miljarderna.

Ett svårare sätt

Ett annat tillvägagångssätt du kan ta är att sluta använda en IDENTITY kolumn helt och hållet; istället kan du konvertera till att använda en SEQUENCE . Du kan skapa en ny bigint ställ in standardvärdet till nästa värde från en SEQUENCE , uppdatera alla dessa värden med värdena från den ursprungliga kolumnen (i partier om det behövs), släpp den ursprungliga kolumnen och byt namn på den nya kolumnen. Låt oss skapa den här fiktiva tabellen och infoga en enda rad:

SKAPA TABELL dbo.SequenceDemo( ID int IDENTITY(1,1), x char(1), CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED (ID));GO INSERT dbo.SequenceDemo(x) VALUES('x');

Därefter skapar vi en SEQUENCE som börjar precis utanför den övre gränsen för en int:

SKAPA SEKVENS dbo.BeyondIntAS bigintSTART MED 2147483648 ÖKA MED 1;

Därefter de ändringar i tabellen som krävs för att byta till att använda SEQUENCE för den nya kolumnen:

BÖRJA TRANSAKTIONEN; -- lägg till en ny "identitet" kolumn:ALTER TABLE dbo.SequenceDemo ADD ID2 bigint;GO -- ställ in den nya kolumnen lika med de befintliga identitetsvärdena-- för stora tabeller, kan behöva göra detta i batcher:UPDATE dbo.SequenceDemo SET ID2 =ID; -- gör det nu inte nullbart och lägg till standard från vår SEQUENCE:ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 bigint NOT NULL;ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity DEFAULT NÄSTA VÄRDE FÖR dbo.BeyondInt FÖR ID2; -- måste släppa den befintliga PK (och eventuella index):ALTER TABLE dbo.SequenceDemo DROP CONSTRAINT PK_SD_Identity; -- släpp den gamla kolumnen och byt namn på den nya:ALTER TABLE dbo.SequenceDemo DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN'; -- lägg nu upp PK:n:ALTER TABLE dbo.SequenceDemo LÄGG TILL BEGRÄNSNING PK_SD_Identity PRIMÄRNYCKEL CLUSTERED (ID); BETA TRANSAKTION;

I det här fallet skulle nästa infogning ge följande resultat (observera att SCOPE_IDENTITY() returnerar inte längre ett giltigt värde):

INSERT dbo.SequenceDemo(x) VALUES('y');SELECT Si =SCOPE_IDENTITY();SELECT ID, x FROM dbo.SequenceDemo; /* resultat Si----NULL ID x---------- -1 x2147483648 y */

Om tabellen är stor och du behöver uppdatera den nya kolumnen i omgångar istället för ovanstående engångstransaktion, som jag har beskrivit här – så att användare kan interagera med tabellen under tiden – måste du ha en trigger på plats för att åsidosätta SEQUENCE värde för alla nya rader som infogas, så att de fortsätter att matcha det som matas ut till valfri anropskod. (Detta förutsätter också att du fortfarande har lite utrymme i heltalsintervallet för att fortsätta acceptera vissa uppdateringar; annars, om du redan har uttömt intervallet, måste du ta lite driftstopp – eller använda den enkla lösningen ovan på kort sikt .)

Låt oss släppa allt och börja om och sedan lägga till den nya kolumnen:

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 PRIMARY KEY CLUSTERED (ID));GO INSERT dbo .SequenceDemo(x) VALUES('x');GO CREATE SEQUENCE dbo.BeyondIntAS bigintSTART MED 2147483648 ÖKA MED 1;GO ALTER TABLE dbo.SequenceDemo ADD ID2 bigint;GO

Och här är triggern som vi lägger till:

SKAPA TRIGGER dbo.After_SequenceDemoON dbo.SequenceDemoAFTER INSERTASBEGIN UPPDATERA sd SET sd.ID2 =sd.ID FRÅN dbo.SequenceDemo AS sd INNER JOIN infogat SOM i ON sd.ID =i.ID;END

Den här gången kommer nästa infogning att fortsätta att generera rader i det lägre intervallet av heltal för båda kolumnerna, tills alla befintliga värden har uppdaterats och resten av ändringarna har genomförts:

INSERT dbo.SequenceDemo(x) VALUES('y');SELECT Si =SCOPE_IDENTITY();SELECT ID, ID2, x FROM dbo.SequenceDemo; /* resultat Si----2 ID ID2 x---- ---- --1 NULL x2 2 y */

Nu kan vi fortsätta att uppdatera den befintliga ID2 värden medan nya rader fortsätter att infogas inom det lägre intervallet:

STÄLL IN NOCOUNT PÅ; DEKLARE @r INT =1; MEDAN @r> 0BÖRJA BÖRJA TRANSAKTION; UPPDATERA TOP (10000) dbo.SequenceDemo SET ID2 =ID DÄR ID2 ÄR NULL; SET @r =@@ROWCOUNT; BETA TRANSAKTION; -- KONTROLLPUNKT; -- om enkelt -- BACKUP LOG ... -- om fullEND

När vi har uppdaterat alla befintliga rader kan vi fortsätta med resten av ändringarna och sedan släppa utlösaren:

BÖRJA TRANSACTION;ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 BIGINT NOT NULL;ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity DEFAULT NÄSTA VÄRDE FÖR 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ÄRNYCKEL CLUSTERED (ID);DROP TRIGGER_DBO.InsteadOf; före> 

Nu kommer nästa infogning att generera dessa värden:

INSERT dbo.SequenceDemo(x) VALUES('z');SELECT Si =SCOPE_IDENTITY();SELECT ID, x FROM dbo.SequenceDemo; /* resultat Si----NULL ID x---------- -1 x2 y2147483648 z */

Om du har kod som bygger på SCOPE_IDENTITY() , @@IDENTITY , eller IDENT_CURRENT() , skulle det också behöva ändras, eftersom dessa värden inte längre fylls i efter en infogning – även om OUTPUT klausul bör fortsätta att fungera korrekt i de flesta scenarier. Om du behöver din kod för att fortsätta tro genererar tabellen en IDENTITY värde, då kan du använda en utlösare för att fejka detta – men det skulle bara kunna fylla i @@IDENTITY vid infogning, inte SCOPE_IDENTITY() . Detta kan fortfarande kräva ändringar, eftersom du i de flesta fall inte vill lita på @@IDENTITY för vad som helst (så om du ska göra ändringar, ta bort alla antaganden om en IDENTITY kolumn alls).

SKAPA TRIGGER dbo.FakeIdentityON dbo.SequenceDemoINSTEAD OF INSERTASBEGIN SET NOCOUNT ON; DECLARE @lowestID bigint =(SELECT MIN(id) FROM infogat); DECLARE @sql nvarchar(max) =N'DECLARE @foo TABLE(ID bigint IDENTITY(' + CONVERT(varchar(32), @lowestID) + N',1));'; VÄLJ @sql +=N'INSERT @foo STANDARDVÄRDEN;' FROM insatt; EXEC sys.sp_executesql @sql; INSERT dbo.SequenceDemo(ID, x) SELECT ID, x FROM infogat;END

Nu kommer nästa infogning att generera dessa värden:

INSERT dbo.SequenceDemo(x) VALUES('a');SELECT Si =SCOPE_IDENTITY(), Ident =@@IDENTITY;SELECT ID, x FROM dbo.SequenceDemo; /* resultat Si Ident---- -----NULL 2147483649 ID x---------- -1 x2 y2147483648 z2147483649 a */

Med den här lösningen skulle du fortfarande behöva hantera andra begränsningar, index och tabeller med inkommande främmande nycklar. Lokala begränsningar och index är ganska enkla, men jag kommer att ta itu med den mer komplexa situationen med främmande nycklar i nästa del av den här serien.

En som inte fungerar, men jag önskar att den skulle göra det

ALTER TABLE SWITCH kan vara ett mycket kraftfullt sätt att göra vissa metadataändringar som är svåra att genomföra annars. Och i motsats till vad många tror, ​​involverar detta inte bara partitionering, och är inte begränsat till Enterprise Edition. Följande kod fungerar på Express och är en metod som folk har använt för att lägga till eller ta bort IDENTITY egendom på ett bord (återigen, tar inte hänsyn till främmande nycklar och alla andra irriterande blockerare).

CREATE TABLE dbo.WithIdentity( ID int IDENTITY(1,1) NOT NULL); CREATE TABLE dbo.WithoutIdentity( ID int NOT NULL); ALTER TABLE dbo.WithIdentity BYTTA TILL dbo.WithoutIdentity;GO SLIP TABLE dbo.WithIdentity;EXEC sys.sp_rename N'dbo.WithoutIdentity', N'dbo.WithIdentity', 'OBJECT';

Detta fungerar eftersom datatyperna och nollbarheten matchar exakt, och ingen uppmärksamhet ägnas åt IDENTITY attribut. Försök dock att blanda datatyper, så fungerar det inte så bra:

CREATE TABLE dbo.SourceTable( ID int IDENTITY(1,1) NOT NULL); CREATE TABLE dbo.TrySwitch( ID bigint IDENTITY(1,1) NOT NULL); ALTER TABLE dbo.SourceTable SWITCH TO dbo.TrySwitch;

Detta resulterar i:

Msg 4944, nivå 16, tillstånd 1
ALTER TABLE SWITCH-satsen misslyckades eftersom kolumn 'ID' har datatypen int i källtabellen 'dbo.SourceTable' som skiljer sig från dess typ bigint i måltabellen 'dbo.TrySwitch'.

Det skulle vara fantastiskt om en SWITCH operation skulle kunna användas i ett scenario som detta, där den enda skillnaden i schemat faktiskt inte *krävde* några fysiska förändringar för att tillgodose (igen, som jag visade i del 1, data skrivs om till nya sidor, även om det finns inget behov av att göra det).

Slutsats

Det här inlägget undersökte två möjliga lösningar för att antingen köpa dig tid innan du ändrar din befintliga IDENTITY kolumn eller överge IDENTITY helt och hållet just nu till förmån för en SEQUENCE . Om ingen av dessa lösningar är acceptabel för dig, vänligen titta på del 4, där vi kommer att ta itu med det här problemet direkt.

[ Del 1 | Del 2 | Del 3 | Del 4 ]


  1. Åtgärda "FEL:  varje UNION-fråga måste ha samma antal kolumner" i PostgreSQL

  2. MariaDB SESSION_USER() Förklarad

  3. java.sql.SQLException:Ingen lämplig drivrutin hittades för jdbc:microsoft:sqlserver

  4. Hur återställer man postgres primära nyckelsekvens när den faller ur synk?