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

Ridurre al minimo l'impatto dell'ampliamento di una colonna IDENTITY - parte 2

[ Parte 1 | Parte 2 | Parte 3 | Parte 4]

Nella prima parte di questa serie, ho mostrato cosa succede a una pagina fisica quando si cambia una colonna IDENTITY da int a bigint. Per semplificare le cose, ho creato un heap molto semplice senza indici o vincoli. Sfortunatamente, la maggior parte di noi non ha quel tipo di lusso:un tavolo importante che deve cambiare ma non può essere semplicemente ricreato da zero probabilmente ha più attributi che ci ostacolano direttamente. In questo post, ho voluto mostrare quelli più comuni, senza nemmeno entrare in cose esotiche come In-Memory OLTP e Columnstore.

Chiave primaria

Si spera che tutte le tue tabelle abbiano una chiave primaria; se è coinvolta la colonna IDENTITY, tuttavia, non sarà così facile alterare il tipo di dati sottostante. Prendi questi semplici esempi, chiavi primarie sia cluster che non cluster:

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

Se provo a cambiare la colonna:

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

Ottengo un paio di messaggi di errore per ogni ALTER (mostrando solo la prima coppia):

Msg 5074, livello 16, stato 1
L'oggetto 'PK_1' dipende dalla colonna 'ID'.
Msg 4922, livello 16, stato 9
ALTER TABLE ALTER COLUMN ID non riuscito perché uno o più oggetti accedono a questa colonna.

Riepilogo:dovremo eliminare la chiave primaria , indipendentemente dal fatto che sia o meno raggruppato.

Indici

Per prima cosa prendiamo un paio di tabelle come sopra e usiamo un indice univoco invece di una chiave primaria:

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

L'esecuzione di comandi ALTER simili sopra, porta agli stessi messaggi di errore. Questo rimane vero anche se disabilito gli indici:

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

Risultati simili per vari altri tipi di combinazioni di indici, come una colonna inclusa o un filtro:

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;

Riepilogo:dovremo eliminare e ricreare tutti gli indici , in cluster o meno, che fanno riferimento alla colonna IDENTITY – nella chiave o nel INCLUDE. Se la colonna IDENTITY fa parte dell'indice cluster, significa tutti gli indici , poiché faranno tutti riferimento alla chiave di clustering per definizione. E disabilitarli non basta.

Colonne calcolate

Anche se questo dovrebbe essere relativamente raro, ho visto colonne calcolate in base alla colonna IDENTITY. Ad esempio:

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

Questa volta, quando proviamo a modificare, otteniamo la stessa coppia di errori, ma con un testo leggermente diverso:

Msg 5074, livello 16, stato 1
La colonna 'NextID' dipende dalla colonna 'ID'.
Msg 4922, livello 16, stato 9
ALTER TABLE ALTER COLUMN ID non riuscito perché uno o più oggetti accedono a questa colonna.

Questo vale anche se modifichiamo la definizione della colonna calcolata in modo che corrisponda al tipo di dati di destinazione:

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

Riepilogo:dovremo modificare le definizioni delle colonne calcolate o eliminarle del tutto.

Viste indicizzate

Le viste indicizzate vedono anche la loro giusta quota di utilizzo. Costruiamo una vista indicizzata che non faccia nemmeno riferimento alla colonna IDENTITY (non notare altri indici o vincoli sulla tabella di base):

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

Ancora una volta, proveremo l'ALTER e questa volta ci riesce . Confesso che sono rimasto sorpreso da questo, poiché SCHEMABINDING dovrebbe impedire qualsiasi modifica alla tabella sottostante, ma in questo caso si applica solo alle colonne esplicitamente referenziate nella vista. Se creiamo una vista leggermente diversa:

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

Ora falliremo a causa della dipendenza della colonna:

Msg 5074, livello 16, stato 1
L'oggetto 'vTest9B' dipende dalla colonna 'ID'.
Msg 4922, livello 16, stato 9
ALTER TABLE ALTER COLUMN ID non riuscito perché uno o più oggetti accedono a questa colonna.

Riepilogo:dovremo eliminare tutti gli indici su tutte le viste che fanno esplicito riferimento alla colonna IDENTITY , oltre a tutti gli indici in qualsiasi vista che fa riferimento alla colonna IDENTITY nel relativo indice cluster.

Chiavi esterne in entrata

Probabilmente l'aspetto più problematico delle chiavi primarie IDENTITY è che, per la natura stessa dei surrogati, l'intero punto è spesso utilizzare questa chiave surrogata in più tabelle correlate. Ora, non intendo sostenere l'evitare l'integrità referenziale, ma potenzialmente potrebbe ostacolarci un po' anche qui. Sappiamo dall'alto che non possiamo modificare una colonna che fa parte di una chiave primaria o di un vincolo univoco e affinché un'altra tabella punti qui con un vincolo di chiave esterna, una di queste due cose deve esistere. Quindi supponiamo di avere le seguenti due tabelle:

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

Prima ancora di poter considerare la modifica del tipo di dati della colonna, dobbiamo eliminare il vincolo:

ALTER TABLE dbo.TestParent DROP CONSTRAINT PK_Parent;

E ovviamente non possiamo, senza eliminare anche il vincolo della chiave esterna, perché questo produce il seguente messaggio di errore:

Msg 3725, livello 16, stato 0
Il vincolo 'PK_Parent' è referenziato dalla tabella 'TestChild', vincolo di chiave esterna 'FK_Parent'.
Msg 3727, livello 16, stato 0
Potrebbe non cadere vincolo. Vedi errori precedenti.

Questo errore persiste anche se prima disabilitiamo il vincolo di chiave esterna:

ALTER TABLE dbo.TestChild NOCHECK CONSTRAINT FK_Parent;

Inoltre, considera che avrai bisogno delle colonne di riferimento per cambiare anche il loro tipo di dati. Inoltre, è probabile che tali colonne partecipino ad alcuni degli elementi precedenti che potrebbero impedire in modo simile la modifica nelle tabelle figlio. Per ottenere le cose completamente copacetiche e sincronizzate, dovremo:

  • rilascia i vincoli e gli indici rilevanti nella tabella padre
  • elimina i relativi vincoli di chiave esterna sulle tabelle figlio
  • elimina tutti gli indici sulle tabelle figlie che fanno riferimento alla colonna FK (e gestiscono eventuali colonne calcolate/viste indicizzate rilevanti)
  • modifica il tipo di dati sulle tabelle padre e tutte le tabelle secondarie
  • Ricrea tutto

Riepilogo:dovremo eliminare le chiavi esterne in arrivo e, potenzialmente, questo avrà tutta una serie di effetti a cascata. Disabilitare semplicemente le chiavi esterne non è sufficiente e non sarebbe comunque una soluzione permanente, perché il tipo di dati dovrà eventualmente cambiare anche nelle tabelle figlie.

Conclusione

So che sembra che ci stiamo muovendo lentamente e ammetto che in questo post mi sembra di allontanarmi da una soluzione piuttosto che verso una. Ci arriverò, ci sono solo molte informazioni da presentare prima, comprese le cose che rendono difficile questo tipo di cambiamento. Eliminato dai riepiloghi sopra, dovremo:

  • rilascia e ricrea gli indici rilevanti nella tabella principale
  • modifica o elimina le colonne calcolate che coinvolgono la colonna IDENTITY
  • rilascia gli indici sulle viste indicizzate che fanno riferimento alla colonna IDENTITY
  • gestire le chiavi esterne in entrata che puntano alla colonna IDENTITY

Sfortunatamente, molte di queste cose sono catch-22. Non puoi modificare una colonna perché un indice si basa su di essa e non puoi modificare l'indice finché la colonna non è cambiata. Non sarebbe fantastico se ALTER INDEX supportasse REBUILD WITH (ONLINE = ON, CHANGE_COLUMN (COLUMN = ID, NEW_TYPE = BIGINT)) ? E CASCADE_CHANGE_TO_REFERENCING_KEYS,COLUMNS,INDEXES,VIEWS,ETC ? Bene, non lo fa (ho controllato). Quindi, dobbiamo trovare modi per rendere queste cose più facili. Per favore, resta sintonizzato per la Parte 3.

[ Parte 1 | Parte 2 | Parte 3 | Parte 4]