Database
 sql >> Base de données >  >> RDS >> Database

Minimiser l'impact de l'élargissement d'une colonne IDENTITY - partie 2

[ Partie 1 | Partie 2 | Partie 3 | Partie 4 ]

Dans la première partie de cette série, j'ai montré ce qui arrive à une page physique lors du changement d'une colonne IDENTITY d'un int à un bigint. Pour garder les choses simples, j'ai créé un tas très simple sans index ni contraintes. Malheureusement, la plupart d'entre nous n'ont pas ce genre de luxe - une table importante qui doit changer mais qui ne peut pas simplement être recréée à partir de zéro a probablement plusieurs attributs qui se dressent directement sur notre chemin. Dans cet article, je voulais montrer les plus courants, sans même entrer dans des choses exotiques comme In-Memory OLTP et Columnstore.

Clé primaire

J'espère que toutes vos tables ont une clé primaire ; si la colonne IDENTITY est impliquée, cependant, il ne sera pas si facile de modifier le type de données sous-jacent. Prenez ces exemples simples, à la fois des clés primaires clusterisées et non clusterisées :

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

Si j'essaye de changer la colonne :

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

Je reçois une paire de messages d'erreur pour chaque ALTER (montrant juste la première paire) :

Msg 5074, Niveau 16, État 1
L'objet 'PK_1' dépend de la colonne 'ID'.
Msg 4922, Niveau 16, État 9
ALTER TABLE ALTER COLUMN ID a échoué car un ou d'autres objets accèdent à cette colonne.

Résumé :nous devrons supprimer la clé primaire , qu'il soit groupé ou non.

Index

Prenons d'abord quelques tables comme ci-dessus, et utilisons un index unique au lieu d'une clé primaire :

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'exécution de commandes ALTER similaires ci-dessus entraîne les mêmes messages d'erreur. Cela reste vrai même si je désactive les index :

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

Résultats similaires pour divers autres types de combinaisons d'index, comme une colonne incluse ou un filtre :

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;

Résumé :nous devrons supprimer et recréer tous les index , groupés ou non, qui référencent la colonne IDENTITY - dans la clé ou INCLUDE. Si la colonne IDENTITY fait partie de l'index clusterisé, cela signifie tous les index , puisqu'ils feront tous référence à la clé de clustering par définition. Et les désactiver ne suffit pas.

Colonnes calculées

Bien que cela devrait être relativement rare, j'ai vu des colonnes calculées basées sur la colonne IDENTITY. Par exemple :

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

Cette fois, lorsque nous essayons de modifier, nous obtenons la même paire d'erreurs, mais avec un texte légèrement différent :

Msg 5074, Niveau 16, État 1
La colonne 'NextID' dépend de la colonne 'ID'.
Msg 4922, Niveau 16, État 9
ALTER TABLE ALTER COLUMN ID a échoué car un ou d'autres objets accèdent à cette colonne.

Cela est même vrai si nous modifions la définition de la colonne calculée pour qu'elle corresponde au type de données cible :

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

Résumé :nous devrons modifier les définitions des colonnes calculées, ou les supprimer complètement.

Vues indexées

Les vues indexées voient également leur juste part d'utilisation. Construisons une vue indexée qui ne fait même pas référence à la colonne IDENTITY (notez qu'il n'y a pas d'autres index ou contraintes sur la table de 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);

Encore une fois, nous allons essayer ALTER, et cette fois ça réussit . J'avoue que j'ai été surpris par cela, car SCHEMABINDING est censé empêcher toute modification de la table sous-jacente, mais dans ce cas, il ne s'applique qu'aux colonnes explicitement référencées dans la vue. Si nous créons une vue légèrement différente :

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

Nous allons maintenant échouer en raison de la dépendance de la colonne :

Msg 5074, Niveau 16, État 1
L'objet 'vTest9B' dépend de la colonne 'ID'.
Msg 4922, Niveau 16, État 9
ALTER TABLE ALTER COLUMN ID a échoué car un ou d'autres objets accèdent à cette colonne.

Résumé :nous devrons supprimer tous les index sur toutes les vues qui référencent explicitement la colonne IDENTITY , ainsi que tous les index sur toute vue faisant référence à la colonne IDENTITY dans son index clusterisé.

Clés étrangères entrantes

L'aspect probablement le plus problématique des clés primaires IDENTITY est que, de par la nature même des substituts, l'intérêt est souvent d'utiliser cette clé de substitution dans plusieurs tables liées. Maintenant, je ne suis pas sur le point de préconiser d'éviter l'intégrité référentielle, mais cela va potentiellement nous gêner un peu ici aussi. Nous savons d'en haut que nous ne pouvons pas modifier une colonne qui fait partie d'une clé primaire ou d'une contrainte unique, et pour qu'une autre table pointe ici avec une contrainte de clé étrangère, l'une de ces deux choses doit exister. Supposons donc que nous ayons les deux tables suivantes :

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

Avant même d'envisager de modifier le type de données de la colonne, nous devons supprimer la contrainte :

ALTER TABLE dbo.TestParent DROP CONSTRAINT PK_Parent;

Et bien sûr, nous ne pouvons pas, sans supprimer également la contrainte de clé étrangère, car cela génère le message d'erreur suivant :

Msg 3725, Niveau 16, État 0
La contrainte 'PK_Parent' est référencée par la table 'TestChild', contrainte de clé étrangère 'FK_Parent'.
Msg 3727, Niveau 16, État 0
Pourrait ne pas supprimer la contrainte. Voir les erreurs précédentes.

Cette erreur persiste même si nous désactivons d'abord la contrainte de clé étrangère :

ALTER TABLE dbo.TestChild NOCHECK CONSTRAINT FK_Parent;

En plus de cela, considérez que vous aurez également besoin des colonnes de référence pour modifier leur type de données. De plus, ces colonnes participent probablement à certains des éléments ci-dessus qui pourraient également empêcher le changement sur les tables enfants. Pour que les choses soient parfaitement compatibles et synchronisées, nous allons devoir :

  • déposez les contraintes et les index pertinents sur la table parent
  • supprimer les contraintes de clé étrangère pertinentes sur les tables enfants
  • supprimez tous les index sur les tables enfants qui référencent la colonne FK (et traitez toutes les colonnes calculées/vues indexées pertinentes)
  • modifier le type de données sur les tables parent et toutes les tables enfant
  • tout recréer

Résumé :nous devrons supprimer les clés étrangères entrantes et, potentiellement, cela aura toute une série d'effets en cascade. Désactiver simplement les clés étrangères n'est pas suffisant et ne serait pas une solution permanente de toute façon, car le type de données devra éventuellement changer également dans les tables enfants.

Conclusion

Je sais qu'il semble que nous avancions lentement, et je reconnais que dans ce post, je semble m'éloigner d'une solution plutôt que vers une solution. J'y arriverai, il y a juste beaucoup d'informations à présenter en premier, y compris les choses qui rendent ce type de changement difficile. À partir des résumés ci-dessus, nous devrons :

  • supprimer et recréer les index pertinents sur la table principale
  • modifier ou supprimer les colonnes calculées qui impliquent la colonne IDENTITY
  • supprimer les index sur les vues indexées qui référencent la colonne IDENTITY
  • traite les clés étrangères entrantes qui pointent vers la colonne IDENTITY

Malheureusement, beaucoup de ces choses sont catch-22. Vous ne pouvez pas modifier une colonne car un index en dépend et vous ne pouvez pas modifier l'index tant que la colonne n'a pas été modifiée. Ne serait-il pas formidable si ALTER INDEX supportait REBUILD WITH (ONLINE = ON, CHANGE_COLUMN (COLUMN = ID, NEW_TYPE = BIGINT)) ? Et CASCADE_CHANGE_TO_REFERENCING_KEYS,COLUMNS,INDEXES,VIEWS,ETC ? Eh bien, ce n'est pas le cas (j'ai vérifié). Nous devons donc trouver des moyens de faciliter ces choses. Veuillez rester à l'écoute pour la partie 3.

[ Partie 1 | Partie 2 | Partie 3 | Partie 4 ]