sql >> Database >  >> RDS >> Database

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

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

In het eerste deel van deze serie heb ik laten zien wat er gebeurt met een fysieke pagina wanneer een IDENTITY-kolom wordt gewijzigd van een int naar een bigint. Om het simpel te houden, heb ik een heel eenvoudige hoop gemaakt zonder indexen of beperkingen. Helaas hebben de meesten van ons dat soort luxe niet - een belangrijke tafel die moet worden veranderd maar niet eenvoudig opnieuw kan worden gemaakt, heeft waarschijnlijk meerdere attributen die ons direct in de weg staan. In dit bericht wilde ik de meest voorkomende laten zien, zonder zelfs maar in te gaan op exotische dingen zoals In-Memory OLTP en Columnstore.

Primaire sleutel

Hopelijk hebben al je tabellen een primaire sleutel; als het echter om de IDENTITY-kolom gaat, is het niet zo eenvoudig om het onderliggende gegevenstype te wijzigen. Neem deze eenvoudige voorbeelden, zowel geclusterde als niet-geclusterde primaire sleutels:

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

Als ik de kolom probeer te veranderen:

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

Ik krijg een paar foutmeldingen voor elke ALTER (laat alleen het eerste paar zien):

Msg 5074, Level 16, State 1
Het object 'PK_1' is afhankelijk van kolom 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID is mislukt omdat een of meer objecten hebben toegang tot deze kolom.

Samenvatting:we moeten de primaire sleutel verwijderen , al dan niet is geclusterd.

Indexen

Laten we eerst een paar tabellen nemen zoals hierboven, en een unieke index gebruiken in plaats van een primaire sleutel:

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

Het uitvoeren van soortgelijke ALTER-opdrachten hierboven, leidt tot dezelfde foutmeldingen. Dit blijft waar, zelfs als ik de indexen uitschakel:

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

Vergelijkbare resultaten voor verschillende andere typen indexcombinaties, zoals een opgenomen kolom of een 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;

Samenvatting:we zullen indexen moeten verwijderen en opnieuw moeten maken , geclusterd of niet, die verwijzen naar de IDENTITY-kolom - in de sleutel of de INCLUDE. Als de IDENTITY-kolom deel uitmaakt van de geclusterde index, betekent dit alle indexen , omdat ze allemaal per definitie naar de clustersleutel verwijzen. En ze uitschakelen is niet genoeg.

Berekende kolommen

Hoewel dit relatief zeldzaam zou moeten zijn, heb ik berekende kolommen gezien op basis van de IDENTITY-kolom. Bijvoorbeeld:

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

Deze keer, wanneer we proberen te wijzigen, krijgen we hetzelfde paar fouten, maar met een iets andere tekst:

Msg 5074, Level 16, State 1
De kolom 'NextID' is afhankelijk van de kolom 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID is mislukt omdat een of meer objecten hebben toegang tot deze kolom.

Dit geldt zelfs als we de berekende kolomdefinitie wijzigen zodat deze overeenkomt met het doelgegevenstype:

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

Samenvatting:we moeten de definities van berekende kolommen wijzigen of ze helemaal laten vallen.

Geïndexeerde weergaven

Geïndexeerde weergaven zien ook hun deel van het gebruik. Laten we een geïndexeerde weergave maken die niet eens verwijst naar de IDENTITY-kolom (let op geen andere indexen of beperkingen in de basistabel):

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

Nogmaals, we proberen de ALTER, en deze keer het lukt . Ik moet bekennen dat ik hierdoor verrast was, aangezien SCHEMABINDING verondersteld wordt wijzigingen aan de onderliggende tabel te voorkomen, maar in dit geval is het alleen van toepassing op kolommen waarnaar expliciet wordt verwezen in de weergave. Als we een iets andere weergave maken:

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

Nu zullen we falen vanwege de kolomafhankelijkheid:

Msg 5074, Level 16, State 1
Het object 'vTest9B' is afhankelijk van kolom 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID is mislukt omdat een of meer objecten hebben toegang tot deze kolom.

Samenvatting:we moeten alle indexen verwijderen voor weergaven die expliciet verwijzen naar de IDENTITY-kolom , evenals alle indexen op elke weergave die verwijst naar de IDENTITY-kolom in de geclusterde index.

Inkomende buitenlandse sleutels

Waarschijnlijk het meest problematische aspect van de primaire sleutels van IDENTITY is dat door de aard van surrogaten het er vaak om gaat deze surrogaatsleutel in meerdere gerelateerde tabellen te gebruiken. Nu ben ik niet van plan om te pleiten voor het vermijden van referentiële integriteit, maar het kan ons hier ook een beetje in de weg staan. We weten van bovenaf dat we een kolom die deel uitmaakt van een primaire sleutel of een unieke beperking niet kunnen wijzigen, en om een ​​andere tabel hier te laten verwijzen met een externe sleutelbeperking, moet een van die twee dingen bestaan. Dus laten we zeggen dat we de volgende twee tabellen hebben:

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

Voordat we zelfs maar kunnen overwegen het gegevenstype van de kolom te wijzigen, moeten we de beperking laten vallen:

ALTER TABLE dbo.TestParent DROP CONSTRAINT PK_Parent;

En dat kunnen we natuurlijk niet, zonder ook de externe sleutelbeperking te laten vallen, want dit levert de volgende foutmelding op:

Msg 3725, Level 16, State 0
Er wordt verwezen naar de beperking 'PK_Parent' door tabel 'TestChild', externe key constraint 'FK_Parent'.
Msg 3727, Level 16, State 0
Kan geen beperking laten vallen. Zie eerdere fouten.

Deze fout blijft bestaan, zelfs als we eerst de externe sleutelbeperking uitschakelen:

ALTER TABLE dbo.TestChild NOCHECK CONSTRAINT FK_Parent;

Houd er bovendien rekening mee dat u de verwijzingskolommen nodig heeft om ook hun gegevenstype te wijzigen. En verder nemen die kolommen waarschijnlijk deel aan enkele van de bovenstaande elementen die op dezelfde manier de wijziging in de onderliggende tabellen kunnen voorkomen. Om alles volledig copactisch en synchroon te krijgen, moeten we het volgende doen:

  • laat de relevante beperkingen en indexen op de bovenliggende tabel vallen
  • laat de relevante beperkingen voor externe sleutels op de onderliggende tabellen vallen
  • laat alle indexen vallen op onderliggende tabellen die verwijzen naar de FK-kolom (en behandel alle relevante berekende kolommen / geïndexeerde weergaven)
  • wijzig het gegevenstype op bovenliggende en alle onderliggende tabellen
  • alles opnieuw maken

Samenvatting:we moeten inkomende buitenlandse sleutels verwijderen en mogelijk zal dit een hele reeks trapsgewijze effecten hebben. Alleen de externe sleutels uitschakelen is niet genoeg en zou sowieso geen permanente oplossing zijn, omdat het datatype uiteindelijk ook in de onderliggende tabellen moet veranderen.

Conclusie

Ik weet dat het lijkt alsof we langzaam vooruit gaan, en ik geef toe dat ik in dit bericht meer afstand lijkt te nemen van een oplossing dan naar een oplossing. Ik zal er komen, er is gewoon veel informatie om eerst te presenteren, inclusief de dingen die dit soort veranderingen moeilijk maken. Geschrapt uit de bovenstaande samenvattingen, moeten we:

  • zet relevante indexen neer en maak ze opnieuw aan in de hoofdtabel
  • verander of verwijder berekende kolommen die betrekking hebben op de IDENTITY-kolom
  • laat indexen vallen op geïndexeerde weergaven die verwijzen naar de IDENTITY-kolom
  • omgaan met inkomende externe sleutels die verwijzen naar de IDENTITEIT-kolom

Helaas zijn veel van deze dingen catch-22. U kunt een kolom niet wijzigen omdat een index ervan afhankelijk is, en u kunt de index pas wijzigen als de kolom is gewijzigd. Zou het niet geweldig zijn als ALTER INDEX REBUILD WITH (ONLINE = ON, CHANGE_COLUMN (COLUMN = ID, NEW_TYPE = BIGINT)) ? En CASCADE_CHANGE_TO_REFERENCING_KEYS,COLUMNS,INDEXES,VIEWS,ETC ? Nou, dat doet het niet (ik heb het gecontroleerd). We moeten dus manieren vinden om deze dingen gemakkelijker te maken. Blijf ons volgen voor deel 3.

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


  1. Zijn externe sleutels echt nodig in een database-ontwerp?

  2. Hoe de juiste opvulling op de dagnaam in Oracle te verwijderen

  3. Een hint naar PostgreSQL

  4. MariaDB String-functies (volledige lijst)