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

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

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

Im ersten Teil dieser Serie habe ich gezeigt, was mit einer physischen Seite passiert, wenn eine IDENTITY-Spalte von einem int zu einem bigint geändert wird. Um die Dinge einfach zu halten, habe ich einen sehr einfachen Heap ohne Indizes oder Einschränkungen erstellt. Leider haben die meisten von uns diesen Luxus nicht – ein wichtiger Tisch, der geändert werden muss, aber nicht einfach von Grund auf neu erstellt werden kann, hat wahrscheinlich mehrere Attribute, die uns direkt im Weg stehen. In diesem Beitrag wollte ich die gebräuchlicheren zeigen, ohne auch nur auf exotische Dinge wie In-Memory OLTP und Columnstore einzugehen.

Primärschlüssel

Hoffentlich haben alle Ihre Tabellen einen Primärschlüssel; Wenn es jedoch um die IDENTITY-Spalte geht, ist es nicht so einfach, den zugrunde liegenden Datentyp zu ändern. Nehmen Sie diese einfachen Beispiele, sowohl geclusterte als auch nicht geclusterte Primärschlüssel:

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

Wenn ich versuche, die Spalte zu ändern:

ALTER TABLE dbo.Test1 ALTER COLUMN ID BIGINT;
GO
ALTER TABLE dbo.Test2 ALTER COLUMN ID BIGINT;

Ich erhalte ein Paar Fehlermeldungen für jeden ALTER (zeigt nur das erste Paar):

Nachricht 5074, Ebene 16, Status 1
Das Objekt „PK_1“ ist von der Spalte „ID“ abhängig.
Nachricht 4922, Ebene 16, Status 9
ALTER TABLE ALTER COLUMN ID ist fehlgeschlagen, weil eine oder weitere Objekte greifen auf diese Spalte zu.

Zusammenfassung:Wir müssen den Primärschlüssel löschen , ob sie geclustert ist oder nicht.

Indizes

Lassen Sie uns zuerst ein paar Tabellen wie oben nehmen und einen eindeutigen Index anstelle eines Primärschlüssels verwenden:

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

Das Ausführen ähnlicher ALTER-Befehle oben führt zu denselben Fehlermeldungen. Dies gilt auch dann, wenn ich die Indizes deaktiviere:

ALTER INDEX IX_3 ON dbo.Test3 DISABLE;
GO
ALTER INDEX IX_4 ON dbo.Test4 DISABLE;

Ähnliche Ergebnisse für verschiedene andere Arten von Indexkombinationen, z. B. eine eingeschlossene Spalte oder einen 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;

Zusammenfassung:Wir müssen alle Indizes löschen und neu erstellen , geclustert oder nicht, die auf die IDENTITY-Spalte verweisen – im Schlüssel oder im INCLUDE. Wenn die IDENTITY-Spalte Teil des Clustered-Index ist, bedeutet dies alle Indizes , da sie alle per Definition auf den Clusterschlüssel verweisen. Und es reicht nicht, sie zu deaktivieren.

Berechnete Spalten

Obwohl dies relativ selten sein sollte, habe ich berechnete Spalten gesehen, die auf der IDENTITY-Spalte basieren. Zum Beispiel:

CREATE TABLE dbo.Test7
(
  ID INT IDENTITY(1,1),
  NextID AS (ID + 1)
);

Wenn wir dieses Mal versuchen, etwas zu ändern, erhalten wir dasselbe Fehlerpaar, aber mit etwas anderem Text:

Nachricht 5074, Ebene 16, Status 1
Die Spalte „NextID“ ist abhängig von der Spalte „ID“.
Nachricht 4922, Ebene 16, Status 9
ALTER TABLE ALTER COLUMN ID ist fehlgeschlagen, weil eine oder weitere Objekte greifen auf diese Spalte zu.

Dies gilt sogar dann, wenn wir die Definition der berechneten Spalte so ändern, dass sie dem Zieldatentyp entspricht:

CREATE TABLE dbo.Test8
(
  ID INT IDENTITY(1,1),
  NextID AS (CONVERT(BIGINT, ID) + 1)
);

Zusammenfassung:Wir müssen die Definitionen der berechneten Spalten ändern oder ganz löschen.

Indizierte Aufrufe

Indizierte Ansichten sehen auch ihren gerechten Anteil an der Nutzung. Konstruieren wir eine indizierte Ansicht, die nicht einmal auf die IDENTITY-Spalte verweist (beachten Sie keine anderen Indizes oder Einschränkungen für die Basistabelle):

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

Noch einmal versuchen wir ALTER, und dieses Mal erfolgt es . Ich gebe zu, dass mich das überrascht hat, da SCHEMABINDING alle Änderungen an der zugrunde liegenden Tabelle verhindern soll, aber in diesem Fall gilt es nur für Spalten, auf die explizit in der Ansicht verwiesen wird. Wenn wir eine etwas andere Ansicht erstellen:

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

Jetzt scheitern wir an der Spaltenabhängigkeit:

Nachricht 5074, Ebene 16, Status 1
Das Objekt „vTest9B“ ist von der Spalte „ID“ abhängig.
Nachricht 4922, Ebene 16, Status 9
ALTER TABLE ALTER COLUMN ID ist fehlgeschlagen, weil eine oder weitere Objekte greifen auf diese Spalte zu.

Zusammenfassung:Wir müssen alle Indizes für alle Ansichten löschen, die explizit auf die IDENTITY-Spalte verweisen , sowie alle Indizes in jeder Ansicht, die auf die IDENTITY-Spalte in ihrem gruppierten Index verweist.

Eingehende Fremdschlüssel

Der wahrscheinlich problematischste Aspekt von IDENTITY-Primärschlüsseln ist, dass es aufgrund der Natur von Ersatzschlüsseln oft darum geht, diesen Ersatzschlüssel in mehreren verwandten Tabellen zu verwenden. Ich werde jetzt nicht dafür plädieren, die referentielle Integrität zu vermeiden, aber sie wird uns möglicherweise auch hier ein wenig im Weg stehen. Wir wissen von oben, dass wir keine Spalte ändern können, die Teil eines Primärschlüssels oder einer Unique-Einschränkung ist, und damit eine andere Tabelle mit einer Fremdschlüssel-Einschränkung hierher zeigt, muss eines dieser beiden Dinge vorhanden sein. Nehmen wir also an, wir haben die folgenden zwei Tabellen:

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

Bevor wir überhaupt daran denken können, den Datentyp der Spalte zu ändern, müssen wir die Einschränkung aufheben:

ALTER TABLE dbo.TestParent DROP CONSTRAINT PK_Parent;

Und natürlich können wir das nicht, ohne auch die Foreign Key Constraint wegzulassen, denn das ergibt die folgende Fehlermeldung:

Nachricht 3725, Ebene 16, Status 0
Die Einschränkung „PK_Parent“ wird von Tabelle „TestChild“, Fremdschlüsselbeschränkung „FK_Parent“ referenziert.
Nachricht 3727, Ebene 16, Status 0
Könnte Einschränkung nicht fallen lassen. Siehe vorherige Fehler.

Dieser Fehler bleibt auch dann bestehen, wenn wir zuerst die Fremdschlüsselbeschränkung deaktivieren:

ALTER TABLE dbo.TestChild NOCHECK CONSTRAINT FK_Parent;

Bedenken Sie außerdem, dass Sie auch die referenzierenden Spalten benötigen, um ihren Datentyp zu ändern. Und außerdem sind diese Spalten wahrscheinlich an einigen der oben genannten Elemente beteiligt, die in ähnlicher Weise die Änderung an den untergeordneten Tabellen verhindern könnten. Um die Dinge vollständig kotaktisch und synchron zu bekommen, müssen wir:

  • Löschen Sie die relevanten Beschränkungen und Indizes in der übergeordneten Tabelle
  • Löschen Sie die relevanten Fremdschlüsseleinschränkungen für die untergeordneten Tabellen
  • alle Indizes auf untergeordneten Tabellen löschen, die auf die FK-Spalte verweisen (und sich mit allen relevanten berechneten Spalten/indizierten Ansichten befassen)
  • den Datentyp der übergeordneten und aller untergeordneten Tabellen ändern
  • alles neu erstellen

Zusammenfassung:Wir müssen eingehende Fremdschlüssel löschen und möglicherweise wird dies eine ganze Reihe von Kaskadeneffekten haben. Einfach die Fremdschlüssel zu deaktivieren reicht nicht aus und wäre sowieso keine dauerhafte Lösung, da sich der Datentyp irgendwann auch in den untergeordneten Tabellen ändern muss.

Schlussfolgerung

Ich weiß, dass es so aussieht, als würden wir uns langsam bewegen, und ich gebe zu, dass ich mich in diesem Beitrag eher von einer Lösung weg als zu einer hin zu bewegen scheine. Ich werde dorthin kommen, es gibt nur eine Menge Informationen, die zuerst präsentiert werden müssen, einschließlich der Dinge, die diese Art von Veränderung schwierig machen. Aus den obigen Zusammenfassungen gestrichen, müssen wir:

  • relevante Indizes in der Haupttabelle löschen und neu erstellen
  • berechnete Spalten ändern oder löschen, die die IDENTITY-Spalte betreffen
  • Löschen Sie Indizes für indizierte Ansichten, die auf die IDENTITY-Spalte verweisen
  • mit eingehenden Fremdschlüsseln umgehen, die auf die IDENTITY-Spalte zeigen

Leider sind viele dieser Dinge Catch-22. Sie können eine Spalte nicht ändern, weil ein Index darauf angewiesen ist, und Sie können den Index nicht ändern, bis sich die Spalte geändert hat. Wäre es nicht großartig, wenn ALTER INDEX REBUILD WITH (ONLINE = ON, CHANGE_COLUMN (COLUMN = ID, NEW_TYPE = BIGINT)) unterstützen würde ? Und CASCADE_CHANGE_TO_REFERENCING_KEYS,COLUMNS,INDEXES,VIEWS,ETC ? Nun, das tut es nicht (ich habe es überprüft). Also müssen wir Wege finden, diese Dinge einfacher zu machen. Bitte bleiben Sie dran für Teil 3.

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