sql >> Database >  >> RDS >> Database

Het minimaliseren van de impact van het verbreden van een IDENTITEIT-kolom - deel 3

[ Deel 1 | Deel 2 | Deel 3 | Deel 4 ]

Tot dusverre heb ik in deze serie de directe fysieke impact op de pagina aangetoond bij het vergroten of verkleinen van int naar bigint , en herhaalde vervolgens verschillende van de veelvoorkomende blokkers voor deze bewerking. In dit bericht wilde ik twee mogelijke oplossingen onderzoeken:een eenvoudige en een ongelooflijk ingewikkelde.

De gemakkelijke manier

Ik werd een beetje van mijn donder beroofd in een opmerking op mijn vorige bericht - Keith Monroe suggereerde dat je de tabel gewoon naar het lagere negatieve kon verplaatsen grens van het gegevenstype integer, waardoor uw capaciteit voor nieuwe waarden wordt verdubbeld. U kunt dit doen met DBCC CHECKIDENT :

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

Dit zou kunnen werken, ervan uitgaande dat de surrogaatwaarden geen betekenis hebben voor eindgebruikers (of, als ze dat wel doen, dat gebruikers niet in paniek raken door plotseling negatieve getallen te krijgen). Ik veronderstel dat je ze voor de gek zou kunnen houden met uitzicht:

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

Dit betekent dat de gebruiker die ID = -2147483648 . heeft toegevoegd zou eigenlijk +2147483648 . zien , de gebruiker die ID = -2147483647 . heeft toegevoegd zou +2147483649 . zien , enzovoort. U zou echter een andere code moeten aanpassen om er zeker van te zijn dat u de omgekeerde berekening maakt wanneer de gebruiker die ID invoert , bijv.

WIJZIGINGSPROCEDURE dbo.GetRowByID @ID bigintASBEGIN STEL NOCOUNT IN; VERKLAREN @RealID bigint; SET @RealID =CASE WHEN @ID> 2147483647 THEN @ID - (2147483648*2) + 1 ELSE @ID END; SELECT ID, @ID /*, andere kolommen */ FROM dbo.TableName WHERE ID =@RealID;ENDGO

Ik ben niet gek op deze verduistering. Helemaal niet. Het is rommelig, misleidend en foutgevoelig. En het stimuleert inzicht in surrogaatsleutels - in het algemeen IDENTITY waarden mogen niet aan eindgebruikers worden getoond, dus het zou ze niet moeten schelen of ze klant 24, 642, -376 of veel grotere getallen aan weerszijden van nul zijn.

Deze "oplossing" gaat er ook van uit dat je nergens een code hebt die bestelt op de IDENTITY kolom om de meest recent ingevoegde rijen eerst te presenteren, of leidt daaruit af dat de hoogste IDENTITY waarde moet de nieuwste rij zijn. Code die doet vertrouw op de sorteervolgorde van de IDENTITY kolom, expliciet of impliciet (wat meer kan zijn dan u denkt als het de geclusterde index is), zal de rijen niet langer in de verwachte volgorde weergeven - het toont alle rijen die zijn gemaakt na de RESEED , beginnend met de eerste, en dan worden alle rijen weergegeven die zijn gemaakt vóór de RESEED , beginnend met de eerste.

Het belangrijkste voordeel van deze aanpak is dat u het gegevenstype niet hoeft te wijzigen, met als resultaat dat de RESEED wijziging vereist geen wijzigingen in indexen, beperkingen of inkomende externe sleutels.

Het nadeel – naast de hierboven genoemde codewijzigingen natuurlijk – is dat je er alleen op korte termijn tijd mee wint. Uiteindelijk put je ook alle beschikbare negatieve gehele getallen uit. En denk niet dat dit de gebruiksduur van de huidige versie van de tabel verdubbelt in termen van tijd – in veel gevallen versnelt de gegevensgroei en blijft deze niet constant, dus u verbruikt de volgende 2 miljard rijen veel sneller dan de eerste 2 miljard.

Een moeilijkere manier

Een andere benadering die u zou kunnen nemen is om te stoppen met het gebruik van een IDENTITY kolom helemaal; in plaats daarvan zou je kunnen converteren naar het gebruik van een SEQUENCE . Je zou een nieuwe bigint kunnen maken kolom, stel de standaardwaarde in op de volgende waarde van een SEQUENCE , werk al die waarden bij met de waarden uit de oorspronkelijke kolom (indien nodig in batches), laat de oorspronkelijke kolom vallen en hernoem de nieuwe kolom. Laten we deze fictieve tabel maken en een enkele rij invoegen:

MAAK TABEL 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');

Vervolgens maken we een SEQUENCE die net voorbij de bovengrens van een int begint:

CREER SEQUENTIE dbo.BeyondIntAS bigintBEGIN MET 2147483648 VERHOGING MET 1;

Vervolgens de wijzigingen in de tabel die nodig zijn om over te schakelen naar het gebruik van de SEQUENCE voor de nieuwe kolom:

BEGIN TRANSACTIE; -- voeg een nieuwe kolom "identiteit" toe:ALTER TABLE dbo.SequenceDemo ADD ID2 bigint;GO -- stel de nieuwe kolom gelijk aan de bestaande identiteitswaarden -- voor grote tabellen moet u dit mogelijk in batches doen:UPDATE dbo.SequenceDemo SET ID2 =ID; -- maak het nu niet nullable en voeg de standaardwaarde toe van onze SEQUENCE:ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 bigint NOT NULL;ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity DEFAULT VOLGENDE WAARDE VOOR dbo.BeyondInt FOR ID2; -- moet de bestaande PK (en eventuele indexen) laten vallen:ALTER TABLE dbo.SequenceDemo DROP CONSTRAINT PK_SD_Identity; -- laat de oude kolom vallen en hernoem de nieuwe:ALTER TABLE dbo.SequenceDemo DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN'; -- zet nu de PK een back-up:ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED (ID); TRANSACTIE COMMIT;

In dit geval zou de volgende invoeging de volgende resultaten opleveren (merk op dat SCOPE_IDENTITY() geeft niet langer een geldige waarde terug):

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

Als de tabel groot is en u de nieuwe kolom in batches moet bijwerken in plaats van de bovenstaande eenmalige transactie, zoals ik hier heb beschreven - zodat gebruikers in de tussentijd met de tabel kunnen communiceren - heeft u een trigger nodig op zijn plaats om de SEQUENCE . te overschrijven waarde voor alle nieuwe rijen die worden ingevoegd, zodat ze blijven overeenkomen met wat wordt uitgevoerd naar een aanroepende code. (Dit veronderstelt ook dat je nog wat ruimte hebt in het gehele bereik om door te gaan met het accepteren van sommige updates; anders, als je het bereik al hebt uitgeput, moet je wat downtime nemen - of de eenvoudige oplossing hierboven gebruiken op korte termijn .)

Laten we alles laten vallen en opnieuw beginnen, en dan gewoon de nieuwe kolom toevoegen:

DROP TABEL dbo.SequenceDemo;DROP SEQUENCE dbo.BeyondInt;GO MAAK TABEL 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 MAAK SEQUENTIE dbo.BeyondIntAS bigintBEGIN MET 2147483648 VERHOGING MET 1;GA WIJZIG TABEL dbo.SequenceDemo ID2 TOEVOEGEN bigint;GO

En hier is de trigger die we zullen toevoegen:

CREATE TRIGGER dbo.After_SequenceDemoON dbo.SequenceDemoAFTER INSERTASBEGIN UPDATE sd SET sd.ID2 =sd.ID VANUIT dbo.SequenceDemo AS sd INNER JOIN ingevoegd AS i ON sd.ID =i.ID;END

Deze keer blijft de volgende invoeging rijen genereren in het onderste bereik van gehele getallen voor beide kolommen, totdat alle reeds bestaande waarden zijn bijgewerkt en de rest van de wijzigingen zijn doorgevoerd:

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

Nu kunnen we doorgaan met het updaten van de bestaande ID2 waarden terwijl nieuwe rijen binnen het lagere bereik worden ingevoegd:

STEL NOCOUNT IN; VERKLAREN @r INT =1; TERWIJL @r> 0BEGIN BEGIN TRANSACTIE; UPDATE TOP (10000) dbo.SequenceDemo SET ID2 =ID WAAR ID2 NULL IS; SET @r =@@ROWCOUNT; COMMIT TRANSACTIE; -- CONTROLEPUNT; -- indien eenvoudig -- BACK-UP LOG ... -- indien volEND

Zodra we alle bestaande rijen hebben bijgewerkt, kunnen we doorgaan met de rest van de wijzigingen en de trigger laten vallen:

BEGIN TRANSACTIE;WIJZIG TABEL dbo.SequenceDemo ALTER COLUMN ID2 BIGINT NOT NULL;VERANDER TABEL dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity STANDAARD VOLGENDE WAARDE VOOR dbo.BeyondInt VOOR ID2;ALTER TABEL dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity STANDAARD VOLGENDE WAARDE VOOR dbo.BeyondInt VOOR ID2; DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN';ALTER TABEL dbo.SequenceDemo ADD CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED (ID);DROP TRIGGER Sequence.InsteadOf_MIT pre> 

Nu zal de volgende invoeging deze waarden genereren:

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

Als u code heeft die afhankelijk is van SCOPE_IDENTITY() , @@IDENTITY , of IDENT_CURRENT() , zou het ook moeten veranderen, omdat die waarden niet langer worden ingevuld na een invoeging - hoewel de OUTPUT clausule zou in de meeste scenario's correct moeten blijven werken. Als je je code nodig hebt om te blijven geloven dat de tabel een IDENTITY genereert waarde, dan zou je een trigger kunnen gebruiken om dit te faken – maar het zou alleen @@IDENTITY kunnen invullen bij invoegen, niet SCOPE_IDENTITY() . Dit kan nog steeds wijzigingen vereisen, omdat u in de meeste gevallen niet wilt vertrouwen op @@IDENTITY voor wat dan ook (dus als u wijzigingen gaat aanbrengen, verwijder dan alle aannames over een IDENTITY kolom helemaal).

CREER TRIGGER dbo.FakeIdentityON dbo.SequenceDemo IN PLAATS VAN INVOEGENBEGIN STEL NOCOUNT IN; DECLARE @lowestID bigint =(SELECT MIN(id) FROM ingevoegd); DECLARE @sql nvarchar(max) =N'DECLARE @foo TABLE(ID bigint IDENTITY(' + CONVERT(varchar(32), @lowestID) + N',1));'; SELECT @sql +=N'INSERT @foo STANDAARDWAARDEN;' VAN ingevoegd; EXEC sys.sp_executesql @sql; INSERT dbo.SequenceDemo(ID, x) SELECT ID, x FROM ingevoegd;END

Nu zal de volgende invoeging deze waarden genereren:

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

Met deze tijdelijke oplossing moet u nog steeds omgaan met andere beperkingen, indexen en tabellen met inkomende externe sleutels. Lokale beperkingen en indexen zijn vrij eenvoudig, maar ik zal de meer complexe situatie met buitenlandse sleutels in het volgende deel van deze serie behandelen.

Een die niet zal werken, maar ik zou willen dat het zou

ALTER TABLE SWITCH kan een zeer krachtige manier zijn om enkele metagegevenswijzigingen aan te brengen die anders moeilijk te bereiken zijn. En in tegenstelling tot wat vaak wordt gedacht, gaat het hierbij niet alleen om partitionering, en is het niet beperkt tot Enterprise Edition. De volgende code werkt op Express en is een methode die mensen hebben gebruikt om de IDENTITY toe te voegen of te verwijderen eigendom op een tafel (nogmaals, zonder rekening te houden met externe sleutels en al die andere vervelende blokkers).

MAAK TABEL dbo.WithIdentity( ID int IDENTITY(1,1) NOT NULL); MAAK TABEL dbo.WithoutIdentity (ID int NIET NULL); WIJZIG TABEL dbo.WithIdentity SCHAKEL NAAR dbo.WithoutIdentity;GA DROP TABLE dbo.WithIdentity;EXEC sys.sp_rename N'dbo.WithoutIdentity', N'dbo.WithIdentity', 'OBJECT';

Dit werkt omdat de datatypes en nullability exact overeenkomen en er geen aandacht wordt besteed aan de IDENTITY attribuut. Probeer echter gegevenstypen te combineren, en dingen werken niet zo goed:

MAAK TABEL dbo.SourceTable (ID int IDENTITY(1,1) NOT NULL); MAAK TABEL dbo.TrySwitch (ID bigint IDENTITY (1,1) NOT NULL); WIJZIG TABEL dbo.SourceTable SCHAKELEN NAAR dbo.TrySwitch;

Dit resulteert in:

Msg 4944, niveau 16, staat 1
ALTER TABLE SWITCH-instructie is mislukt omdat kolom 'ID' het gegevenstype int heeft in brontabel 'dbo.SourceTable', dat verschilt van het type bigint in doeltabel 'dbo.TrySwitch'.

Het zou fantastisch zijn als een SWITCH operatie zou kunnen worden gebruikt in een scenario als dit, waar het enige verschil in schema eigenlijk geen fysieke veranderingen * vereiste * om tegemoet te komen (nogmaals, zoals ik in deel 1 liet zien, worden de gegevens opnieuw geschreven naar nieuwe pagina's, hoewel dat is niet nodig).

Conclusie

In dit bericht zijn twee mogelijke oplossingen onderzocht om ofwel tijd te winnen voordat je je bestaande IDENTITY wijzigt kolom, of het verlaten van IDENTITY helemaal nu in het voordeel van een SEQUENCE . Als geen van deze tijdelijke oplossingen voor u acceptabel is, kijk dan naar deel 4, waar we dit probleem direct aanpakken.

[ Deel 1 | Deel 2 | Deel 3 | Deel 4 ]


  1. Alle dubbele rijen verwijderen, behalve één in MySQL?

  2. Een tabel maken in SQLite

  3. Hoe TO_BASE64() werkt in MariaDB

  4. Hoe een PostgreSQL-database migreren naar een SQLServer-database?