sql >> Base de Datos >  >> RDS >> Database

Minimizar el impacto de ampliar una columna de IDENTIDAD - parte 2

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

En la primera parte de esta serie, mostré lo que le sucede a una página física cuando se cambia una columna IDENTIDAD de int a bigint. Para simplificar las cosas, creé un montón muy simple sin índices ni restricciones. Desafortunadamente, la mayoría de nosotros no tenemos ese tipo de lujo:una mesa importante que debe cambiarse pero que no puede volver a crearse desde cero probablemente tenga múltiples atributos que se interponen directamente en nuestro camino. En esta publicación, quería mostrar los más comunes, sin siquiera entrar en cosas exóticas como In-Memory OLTP y Columnstore.

Clave principal

Con suerte, todas sus tablas tienen una clave principal; sin embargo, si la columna IDENTIDAD está involucrada, no será tan fácil alterar el tipo de datos subyacente. Tome estos ejemplos simples, tanto claves primarias agrupadas como no agrupadas:

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 intento cambiar la columna:

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

Recibo un par de mensajes de error para cada ALTER (solo se muestra el primer par):

Mensaje 5074, nivel 16, estado 1
El objeto 'PK_1' depende de la columna 'ID'.
Mensaje 4922, nivel 16, estado 9
ALTER TABLE ALTER COLUMN ID falló porque uno o más objetos acceden a esta columna.

Resumen:Tendremos que soltar la clave principal , esté o no agrupado.

Índices

Primero, tomemos un par de tablas como las anteriores y usemos un índice único en lugar de una clave principal:

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

La ejecución de comandos ALTER similares a los anteriores genera los mismos mensajes de error. Esto sigue siendo cierto incluso si deshabilito los índices:

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

Resultados similares para otros tipos de combinaciones de índices, como una columna incluida 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;

Resumen:Tendremos que descartar y volver a crear los índices , agrupados o no, que hacen referencia a la columna IDENTIDAD, en la clave o INCLUYE. Si la columna IDENTIDAD es parte del índice agrupado, esto significa todos los índices , ya que todos ellos harán referencia a la clave de agrupación por definición. Y deshabilitarlos no es suficiente.

Columnas calculadas

Si bien esto debería ser relativamente raro, he visto columnas calculadas basadas en la columna IDENTIDAD. Por ejemplo:

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

Esta vez, cuando tratamos de modificar, obtenemos el mismo par de errores, pero con un texto ligeramente diferente:

Mensaje 5074, nivel 16, estado 1
La columna 'NextID' depende de la columna 'ID'.
Mensaje 4922, nivel 16, estado 9
ALTER TABLE ALTER COLUMN ID falló porque uno o más objetos acceden a esta columna.

Esto es cierto incluso si cambiamos la definición de la columna calculada para que coincida con el tipo de datos de destino:

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

Resumen:Tendremos que cambiar las definiciones de las columnas calculadas o eliminarlas por completo.

Vistas indexadas

Las vistas indexadas también ven su parte justa de uso. Construyamos una vista indexada que ni siquiera haga referencia a la columna IDENTIDAD (tenga en cuenta que no hay otros índices o restricciones en la tabla 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);

Una vez más, probaremos ALTER, y esta vez tiene éxito . Confieso que esto me sorprendió, ya que se supone que SCHEMABINDING evita cualquier cambio en la tabla subyacente, pero en este caso solo se aplica a las columnas a las que se hace referencia explícitamente en la vista. Si creamos una vista ligeramente diferente:

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

Ahora fallaremos debido a la dependencia de la columna:

Mensaje 5074, nivel 16, estado 1
El objeto 'vTest9B' depende de la columna 'ID'.
Mensaje 4922, nivel 16, estado 9
ALTER TABLE ALTER COLUMN ID falló porque uno o más objetos acceden a esta columna.

Resumen:Tendremos que eliminar todos los índices en cualquier vista que haga referencia explícita a la columna IDENTIDAD , así como todos los índices en cualquier vista que haga referencia a la columna IDENTIDAD en su índice agrupado.

Claves foráneas entrantes

Probablemente, el aspecto más problemático de las claves primarias de IDENTIDAD es que, por la propia naturaleza de los sustitutos, el objetivo principal es usar esta clave sustituta en varias tablas relacionadas. Ahora, no voy a abogar por evitar la integridad referencial, pero potencialmente también se interpondrá un poco en nuestro camino aquí. Sabemos desde arriba que no podemos cambiar una columna que es parte de una clave principal o una restricción única, y para que otra tabla apunte aquí con una restricción de clave externa, una de esas dos cosas tiene que existir. Así que digamos que tenemos las siguientes dos tablas:

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

Antes de que podamos siquiera considerar cambiar el tipo de datos de la columna, debemos eliminar la restricción:

ALTER TABLE dbo.TestParent DROP CONSTRAINT PK_Parent;

Y, por supuesto, no podemos, sin eliminar también la restricción de clave externa, porque esto genera el siguiente mensaje de error:

Mensaje 3725, Nivel 16, Estado 0
La restricción 'PK_Parent' está siendo referenciada por la tabla 'TestChild', restricción de clave externa 'FK_Parent'.
Mensaje 3727, Nivel 16, Estado 0
Podría no soltar la restricción. Ver errores anteriores.

Este error permanece incluso si primero deshabilitamos la restricción de clave externa:

ALTER TABLE dbo.TestChild NOCHECK CONSTRAINT FK_Parent;

Además de esto, considere que también necesitará las columnas de referencia para cambiar su tipo de datos. Y además, es probable que esas columnas participen en algunos de los elementos anteriores que podrían evitar de manera similar el cambio en las tablas secundarias. Para que las cosas sean completamente copaceticas y sincronizadas, vamos a tener que:

  • soltar las restricciones e índices relevantes en la tabla principal
  • soltar las restricciones de clave externa relevantes en las tablas secundarias
  • elimine los índices en las tablas secundarias que hagan referencia a la columna FK (y maneje las columnas calculadas/vistas indexadas relevantes)
  • modificar el tipo de datos en las tablas principal y todas las secundarias
  • volver a crear todo

Resumen:Tendremos que eliminar las claves foráneas entrantes y, potencialmente, esto tendrá una gran cantidad de efectos en cascada. Simplemente deshabilitar las claves foráneas no es suficiente, y de todos modos no sería una solución permanente, porque el tipo de datos también tendrá que cambiar en las tablas secundarias eventualmente.

Conclusión

Sé que parece que nos estamos moviendo lentamente, y reconozco que en esta publicación parece que me estoy alejando de una solución en lugar de acercarme a una. Llegaré allí, hay mucha información para presentar primero, incluidas las cosas que dificultan este tipo de cambio. Extraído de los resúmenes anteriores, necesitaremos:

  • soltar y volver a crear índices relevantes en la tabla principal
  • cambie o elimine las columnas calculadas que involucran la columna IDENTIDAD
  • soltar índices en vistas indexadas que hacen referencia a la columna IDENTIDAD
  • tratar con claves foráneas entrantes que apuntan a la columna IDENTIDAD

Desafortunadamente, muchas de estas cosas son catch-22. No puede cambiar una columna porque un índice se basa en ella y no puede cambiar el índice hasta que la columna haya cambiado. ¿No sería genial si ALTER INDEX fuera compatible con REBUILD WITH (ONLINE = ON, CHANGE_COLUMN (COLUMN = ID, NEW_TYPE = BIGINT)) ? Y CASCADE_CHANGE_TO_REFERENCING_KEYS,COLUMNS,INDEXES,VIEWS,ETC ? Bueno, no lo hace (lo comprobé). Por lo tanto, debemos encontrar formas de hacer que estas cosas sean más fáciles. Estén atentos a la Parte 3.

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