Database
 sql >> Datenbank >  >> RDS >> Database

Minimierung der Auswirkungen der Erweiterung einer IDENTITY-Spalte – Teil 3

[ Teil 1 | Teil 2 | Teil 3 | Teil 4 ]

Bisher habe ich in dieser Serie die direkten physischen Auswirkungen auf die Seite beim Upsizing von int demonstriert zu bigint , und dann durch mehrere der gängigen Blocker zu dieser Operation iteriert. In diesem Beitrag wollte ich zwei mögliche Problemumgehungen untersuchen:eine einfache und eine unglaublich komplizierte.

Der einfache Weg

In einem Kommentar zu meinem vorherigen Beitrag wurde ich ein wenig meines Donners beraubt – Keith Monroe schlug vor, dass Sie die Tabelle einfach auf das untere Negativ setzen könnten Grenze des Integer-Datentyps, was Ihre Kapazität für neue Werte verdoppelt. Sie können dies mit DBCC CHECKIDENT tun :

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

Dies könnte funktionieren, vorausgesetzt, die Ersatzwerte haben für die Endbenutzer keine Bedeutung (oder, falls doch, dass die Benutzer nicht ausflippen, wenn sie plötzlich negative Zahlen erhalten). Ich nehme an, Sie könnten sie mit einer Ansicht täuschen:

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;

Dies bedeutet, dass der Benutzer ID = -2147483648 hinzugefügt hat tatsächlich +2147483648 sehen würde , der Benutzer, der ID = -2147483647 hinzugefügt hat würde +2147483649 sehen , und so weiter. Sie müssten jedoch anderen Code anpassen, um sicherzustellen, dass die umgekehrte Berechnung durchgeführt wird, wenn der Benutzer diese ID übergibt , z. B.

ALTER PROCEDURE dbo.GetRowByID @ID bigintASBEGIN SET NOCOUNT ON; DECLARE @RealID bigint; SET @RealID =CASE WHEN @ID> 2147483647 THEN @ID - (2147483648*2) + 1 ELSE @ID END; SELECT ID, @ID /*, andere Spalten */ FROM dbo.TableName WHERE ID =@RealID;ENDGO

Ich bin nicht verrückt nach dieser Verschleierung. Überhaupt. Es ist chaotisch, irreführend und fehleranfällig. Und es fördert die Sichtbarkeit von Ersatzschlüsseln – im Allgemeinen IDENTITY Werte sollten Endbenutzern nicht offengelegt werden, also sollte es ihnen wirklich egal sein, ob es sich um Kunden 24, 642, -376 oder viel größere Zahlen auf beiden Seiten der Null handelt.

Diese "Lösung" geht auch davon aus, dass Sie nirgendwo Code haben, der nach IDENTITY ordnet Spalte, um die zuletzt eingefügten Zeilen zuerst anzuzeigen, oder schließt daraus, dass die höchste IDENTITY Wert muss die neueste Zeile sein. Code, der funktioniert Verlassen Sie sich auf die Sortierreihenfolge der IDENTITY -Spalte, entweder explizit oder implizit (was mehr sein könnte, als Sie denken, wenn es sich um den Clustered-Index handelt), zeigt die Zeilen nicht mehr in der erwarteten Reihenfolge – es werden alle Zeilen angezeigt, die nach dem RESEED erstellt wurden , beginnend mit dem ersten, und dann werden alle Zeilen angezeigt, die vor dem RESEED erstellt wurden , beginnend mit dem ersten.

Der Hauptvorteil dieses Ansatzes besteht darin, dass Sie den Datentyp nicht ändern müssen, und folglich das RESEED Änderung erfordert keine Änderungen an Indizes, Beschränkungen oder eingehenden Fremdschlüsseln.

Die Kehrseite – neben den oben erwähnten Code-Änderungen natürlich – ist, dass man sich damit nur kurzfristig Zeit verschafft. Irgendwann werden Sie auch alle verfügbaren negativen Ganzzahlen ausschöpfen. Und denken Sie nicht, dass dies die Nutzungsdauer der aktuellen Version des Tisches zeitlich verdoppelt – In vielen Fällen beschleunigt sich das Datenwachstum und bleibt nicht konstant, sodass Sie die nächsten 2 Milliarden Zeilen viel schneller verbrauchen als die ersten 2 Milliarden.

Ein schwieriger Weg

Ein anderer Ansatz, den Sie verfolgen könnten, besteht darin, die Verwendung einer IDENTITY einzustellen Spalte insgesamt; stattdessen könnten Sie zur Verwendung einer SEQUENCE konvertieren . Sie könnten einen neuen bigint erstellen -Spalte setzen Sie den Standardwert auf den nächsten Wert aus einer SEQUENCE , aktualisieren Sie alle diese Werte mit den Werten aus der ursprünglichen Spalte (ggf. in Stapeln), löschen Sie die ursprüngliche Spalte und benennen Sie die neue Spalte um. Lassen Sie uns diese fiktive Tabelle erstellen und eine einzelne Zeile einfügen:

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

Als Nächstes erstellen wir eine SEQUENCE die direkt hinter der oberen Grenze eines int beginnt:

SEQUENZ ERSTELLEN dbo.BeyondIntAS bigintSTART WITH 2147483648 INCREMENT BY 1;

Als nächstes die Änderungen in der Tabelle, die notwendig sind, um zur Verwendung der SEQUENCE zu wechseln für die neue Spalte:

TRANSAKTION BEGINNEN; -- fügen Sie eine neue "Identitäts"-Spalte hinzu:ALTER TABLE dbo.SequenceDemo ADD ID2 bigint;GO -- setzen Sie die neue Spalte auf die vorhandenen Identitätswerte -- für große Tabellen müssen Sie dies möglicherweise in Stapeln tun:UPDATE dbo.SequenceDemo SETZE ID2 =ID; -- Machen Sie es jetzt nicht nullfähig und fügen Sie den Standardwert aus unserer SEQUENCE hinzu:ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 bigint NOT NULL; ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity DEFAULT NEXT VALUE FOR dbo.BeyondInt FOR ID2; - Vorhandenes PK (und alle Indizes) löschen müssen:ALTER TABLE dbo.SequenceDemo DROP CONSTRAINT PK_SD_Identity; -- die alte Spalte löschen und die neue umbenennen:ALTER TABLE dbo.SequenceDemo DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN'; -- Setzen Sie jetzt den PK zurück:ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED (ID); TRANSAKTION COMMIT;

In diesem Fall würde die nächste Einfügung die folgenden Ergebnisse liefern (beachten Sie, dass SCOPE_IDENTITY() gibt keinen gültigen Wert mehr zurück):

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

Wenn die Tabelle groß ist und Sie die neue Spalte in Stapeln aktualisieren müssen, anstatt der obigen One-Shot-Transaktion, wie ich hier beschrieben habe – damit Benutzer in der Zwischenzeit mit der Tabelle interagieren können –, benötigen Sie einen Trigger vorhanden, um die SEQUENCE zu überschreiben -Wert für alle neu eingefügten Zeilen, sodass sie weiterhin mit der Ausgabe an jeden aufrufenden Code übereinstimmen. (Dies setzt auch voraus, dass Sie noch etwas Platz im Integer-Bereich haben, um weiterhin einige Updates zu akzeptieren; andernfalls müssen Sie, wenn Sie den Bereich bereits ausgeschöpft haben, etwas Ausfallzeit in Kauf nehmen – oder kurzfristig die einfache Lösung oben verwenden .)

Lass uns alles fallen lassen und neu anfangen, dann füge einfach die neue Spalte hinzu:

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 SEQUENCE ERSTELLEN dbo.BeyondIntAS bigintSTART WITH 2147483648 INCREMENT BY 1;GO ALTER TABLE dbo.SequenceDemo ADD ID2 bigint;GO

Und hier ist der Auslöser, den wir hinzufügen werden:

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

Diesmal generiert die nächste Einfügung weiterhin Zeilen im unteren Bereich von Ganzzahlen für beide Spalten, bis alle bereits vorhandenen Werte aktualisiert und die restlichen Änderungen festgeschrieben wurden:

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

Jetzt können wir mit der Aktualisierung der bestehenden ID2 fortfahren Werte, während im unteren Bereich weiterhin neue Zeilen eingefügt werden:

SET NOCOUNT ON; DECLARE @r INT =1; WHILE @r> 0BEGIN BEGIN TRANSAKTION; UPDATE TOP (10000) dbo.SequenceDemo SET ID2 =ID, WO ID2 NULL IST; SET @r =@@ROWCOUNT; COMMIT-TRANSAKTION; -- KONTROLLPUNKT; -- falls einfach -- BACKUP LOG ... -- falls vollständigEND

Sobald wir alle vorhandenen Zeilen aktualisiert haben, können wir mit den restlichen Änderungen fortfahren und dann den Trigger löschen:

BEGIN TRANSACTION;ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 BIGINT NOT NULL;ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity DEFAULT NEXT VALUE FOR dbo.BeyondInt FOR ID2;ALTER TABLE dbo.SequenceDemo DROP CONSTRAINT PK_SD_Identity;ALTER TABLE dbo.SequenceDemo DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN';ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED (ID);DROP TRIGGER dbo.InsteadOf_SequenceDemoCOMMIT TRANSACTION; 

Nun generiert die nächste Einfügung diese Werte:

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

Wenn Sie Code haben, der auf SCOPE_IDENTITY() angewiesen ist , @@IDENTITY , oder IDENT_CURRENT() , müsste sich auch ändern, da diese Werte nach einer Einfügung nicht mehr gefüllt werden – obwohl die OUTPUT -Klausel sollte in den meisten Szenarien weiterhin ordnungsgemäß funktionieren. Wenn Sie Ihren Code benötigen, um weiterhin zu glauben, generiert die Tabelle eine IDENTITY Wert, dann könnten Sie einen Trigger verwenden, um dies vorzutäuschen – er könnte jedoch nur @@IDENTITY füllen beim Einfügen, nicht SCOPE_IDENTITY() . Dies kann noch Änderungen erfordern, da Sie sich in den meisten Fällen nicht auf @@IDENTITY verlassen möchten für alles (wenn Sie also Änderungen vornehmen möchten, entfernen Sie alle Annahmen über eine IDENTITY Spalte überhaupt).

CREATE TRIGGER dbo.FakeIdentityON dbo.SequenceDemoStatt INSERTASBEGIN SET NOCOUNT ON; DECLARE @lowestID bigint =(SELECT MIN(id) FROM eingefügt); DECLARE @sql nvarchar(max) =N'DECLARE @foo TABLE(ID bigint IDENTITY(' + CONVERT(varchar(32), @lowestID) + N',1));'; SELECT @sql +=N'INSERT @foo STANDARDWERTE;' VON eingefügt; EXEC sys.sp_executesql @sql; INSERT dbo.SequenceDemo(ID, x) SELECT ID, x FROM eingefügt;END

Nun generiert die nächste Einfügung diese Werte:

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

Bei dieser Problemumgehung müssten Sie sich weiterhin mit anderen Einschränkungen, Indizes und Tabellen mit eingehenden Fremdschlüsseln befassen. Lokale Einschränkungen und Indizes sind ziemlich einfach, aber ich werde mich im nächsten Teil dieser Serie mit der komplexeren Situation mit Fremdschlüsseln befassen.

Eine, die nicht funktioniert, aber ich wünschte, sie würde

ALTER TABLE SWITCH kann eine sehr leistungsfähige Möglichkeit sein, einige Metadatenänderungen vorzunehmen, die sonst nur schwer zu bewerkstelligen sind. Und entgegen der landläufigen Meinung geht es nicht nur um die Partitionierung, und es ist nicht auf die Enterprise Edition beschränkt. Der folgende Code funktioniert auf Express und ist eine Methode, die verwendet wurde, um IDENTITY hinzuzufügen oder zu entfernen -Eigenschaft auf einer Tabelle (wieder ohne Berücksichtigung von Fremdschlüsseln und all diesen anderen lästigen Blockern).

CREATE TABLE dbo.WithIdentity( ID int IDENTITY(1,1) NOT NULL); TABELLE ERSTELLEN dbo.WithoutIdentity (ID int NOT NULL); ALTER TABLE dbo.WithIdentity SWITCH TO dbo.WithoutIdentity;GO DROP TABLE dbo.WithIdentity;EXEC sys.sp_rename N'dbo.WithoutIdentity', N'dbo.WithIdentity', 'OBJECT';

Dies funktioniert, weil die Datentypen und die Nullfähigkeit genau übereinstimmen und der IDENTITY keine Beachtung geschenkt wird Attribut. Versuchen Sie jedoch, Datentypen zu mischen, und die Dinge funktionieren nicht so gut:

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

Daraus ergibt sich:

Msg 4944, Level 16, State 1
Die ALTER TABLE SWITCH-Anweisung ist fehlgeschlagen, da die Spalte „ID“ den Datentyp int in der Quelltabelle „dbo.SourceTable“ hat, der sich von ihrem Typ bigint in der Zieltabelle „dbo.TrySwitch“ unterscheidet.

Es wäre fantastisch, wenn ein SWITCH Operation könnte in einem Szenario wie diesem verwendet werden, wo der einzige Unterschied im Schema eigentlich keine physikalischen Änderungen *erfordert* (auch hier werden, wie ich in Teil 1 gezeigt habe, die Daten auf neue Seiten umgeschrieben, obwohl das ist nicht nötig).

Schlussfolgerung

In diesem Beitrag wurden zwei mögliche Problemumgehungen untersucht, um Ihnen entweder Zeit zu verschaffen, bevor Sie Ihre vorhandene IDENTITY ändern Spalte, oder IDENTITY aufgeben insgesamt zugunsten einer SEQUENCE . Wenn keine dieser Problemumgehungen für Sie akzeptabel ist, sehen Sie sich bitte Teil 4 an, in dem wir dieses Problem direkt angehen.

[ Teil 1 | Teil 2 | Teil 3 | Teil 4 ]