Database
 sql >> Base de Dados >  >> RDS >> Database

Minimizando o impacto do alargamento de uma coluna IDENTITY – parte 2


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

Na primeira parte desta série, mostrei o que acontece com uma página física ao alterar uma coluna IDENTITY de int para bigint. Para manter as coisas simples, criei um heap muito simples, sem índices ou restrições. Infelizmente, a maioria de nós não tem esse tipo de luxo - uma mesa importante que precisa mudar, mas não pode simplesmente ser recriada do zero, provavelmente tem vários atributos diretamente em nosso caminho. Neste post, eu queria mostrar os mais comuns, sem nem entrar em coisas exóticas como OLTP In-Memory e Columnstore.

Chave primária


Espero que todas as suas tabelas tenham uma chave primária; se a coluna IDENTITY estiver envolvida, no entanto, não será tão fácil alterar o tipo de dados subjacente. Veja estes exemplos simples, chaves primárias clusterizadas e não clusterizadas:
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 eu tentar mudar a coluna:
ALTER TABLE dbo.Test1 ALTER COLUMN ID BIGINT;
GO
ALTER TABLE dbo.Test2 ALTER COLUMN ID BIGINT;

Recebo um par de mensagens de erro para cada ALTER (apenas mostrando o primeiro par):
Msg 5074, Level 16, State 1
O objeto 'PK_1' depende da coluna 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID falhou porque um ou mais objetos acessam esta coluna.
Resumo:precisaremos descartar a chave primária , esteja ou não agrupado.

Índices


Primeiro, vamos pegar algumas tabelas como acima e usar um índice exclusivo em vez de uma chave primária:
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) 
);

A execução de comandos ALTER semelhantes acima leva às mesmas mensagens de erro. Isso permanece verdadeiro mesmo se eu desabilitar os índices:
ALTER INDEX IX_3 ON dbo.Test3 DISABLE;
GO
ALTER INDEX IX_4 ON dbo.Test4 DISABLE;

Resultados semelhantes para vários outros tipos de combinações de índice, como uma coluna incluída ou um 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;

Resumo:precisaremos eliminar e recriar quaisquer índices , clusterizados ou não, que fazem referência à coluna IDENTITY – na chave ou no INCLUDE. Se a coluna IDENTITY fizer parte do índice clusterizado, isso significa todos os índices , já que todos farão referência à chave de clustering por definição. E desativá-los não é suficiente.

Colunas computadas


Embora isso deva ser relativamente raro, vi colunas computadas com base na coluna IDENTITY. Por exemplo:
CREATE TABLE dbo.Test7
(
  ID INT IDENTITY(1,1),
  NextID AS (ID + 1)
);

Desta vez, quando tentamos alterar, obtemos o mesmo par de erros, mas com um texto ligeiramente diferente:
Msg 5074, Level 16, State 1
A coluna 'NextID' depende da coluna 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID falhou porque um ou mais objetos acessam esta coluna.
Isso é verdade mesmo se alterarmos a definição da coluna computada para corresponder ao tipo de dados de destino:
CREATE TABLE dbo.Test8
(
  ID INT IDENTITY(1,1),
  NextID AS (CONVERT(BIGINT, ID) + 1)
);

Resumo:precisaremos alterar as definições das colunas computadas ou eliminá-las completamente.

Visualizações Indexadas


As visualizações indexadas também veem seu quinhão de uso. Vamos construir uma visão indexada que nem sequer faz referência à coluna IDENTITY (observe nenhum outro índice ou restrição na tabela 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);

Mais uma vez, tentaremos o ALTER, e desta vez conseguiu . Confesso que fiquei surpreso com isso, já que SCHEMABINDING deve impedir qualquer alteração na tabela subjacente, mas nesse caso só se aplica a colunas explicitamente referenciadas na exibição. Se criarmos uma visão ligeiramente 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);

Agora vamos falhar devido à dependência da coluna:
Msg 5074, Level 16, State 1
O objeto 'vTest9B' depende da coluna 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID falhou porque um ou mais objetos acessam esta coluna.
Resumo:precisaremos descartar todos os índices em todas as visualizações que fazem referência explícita à coluna IDENTITY , bem como todos os índices em qualquer exibição que faça referência à coluna IDENTITY em seu índice clusterizado.

Chaves estrangeiras de entrada


Provavelmente, o aspecto mais problemático das chaves primárias IDENTITY é que, pela própria natureza dos substitutos, o objetivo principal é usar essa chave substituta em várias tabelas relacionadas. Agora, eu não estou prestes a defender que se evite a integridade referencial, mas isso potencialmente vai atrapalhar um pouco o nosso caminho aqui também. Sabemos de cima que não podemos alterar uma coluna que faz parte de uma chave primária ou restrição exclusiva, e para que outra tabela aponte aqui com uma restrição de chave estrangeira, uma dessas duas coisas precisa existir. Então, digamos que temos as duas tabelas a seguir:
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 podermos considerar alterar o tipo de dados da coluna, precisamos eliminar a restrição:
ALTER TABLE dbo.TestParent DROP CONSTRAINT PK_Parent;

E é claro que não podemos, sem também descartar a restrição de chave estrangeira, porque isso gera a seguinte mensagem de erro:
Msg 3725, Level 16, State 0
A restrição 'PK_Parent' está sendo referenciada pela tabela 'TestChild', restrição de chave estrangeira 'FK_Parent'.
Msg 3727, Level 16, State 0
Pode não abandone a restrição. Veja os erros anteriores.
Este erro permanece mesmo se primeiro desabilitarmos a restrição de chave estrangeira:
ALTER TABLE dbo.TestChild NOCHECK CONSTRAINT FK_Parent;

Além disso, considere que você também precisará das colunas de referência para alterar seu tipo de dados. Além disso, essas colunas provavelmente participam de alguns dos elementos acima que podem impedir a alteração nas tabelas filhas. Para que as coisas sejam completamente compatíveis e sincronizadas, teremos que:
  • descarte as restrições e índices relevantes na tabela pai
  • descarte as restrições de chave estrangeira relevantes nas tabelas filhas
  • descarte todos os índices em tabelas filhas que fazem referência à coluna FK (e lide com quaisquer colunas computadas relevantes/visualizações indexadas)
  • alterar o tipo de dados nas tabelas pai e em todas as tabelas filhas
  • recriar tudo

Resumo:precisaremos descartar as chaves estrangeiras recebidas e, potencialmente, isso terá uma enorme quantidade de efeitos em cascata. Simplesmente desabilitar as chaves estrangeiras não é suficiente e não seria uma solução permanente de qualquer maneira, porque o tipo de dados também terá que mudar nas tabelas filhas eventualmente.

Conclusão


Eu sei que parece que estamos nos movendo devagar, e admito que neste post eu pareço estar me afastando de uma solução e não em direção a uma. Eu vou chegar lá, tem muita informação para apresentar primeiro, inclusive as coisas que dificultam esse tipo de mudança. Raspado dos resumos acima, precisaremos:
  • descarte e recrie índices relevantes na tabela principal
  • alterar ou eliminar colunas computadas que envolvem a coluna IDENTITY
  • solte índices em visualizações indexadas que fazem referência à coluna IDENTITY
  • lidar com chaves estrangeiras de entrada que apontam para a coluna IDENTITY

Infelizmente, muitas dessas coisas são catch-22. Você não pode alterar uma coluna porque um índice depende dela e não pode alterar o índice até que a coluna seja alterada. Não seria ótimo se ALTER INDEX suportasse REBUILD WITH (ONLINE = ON, CHANGE_COLUMN (COLUMN = ID, NEW_TYPE = BIGINT)) ? E CASCADE_CHANGE_TO_REFERENCING_KEYS,COLUMNS,INDEXES,VIEWS,ETC ? Bem, não (eu verifiquei). Então, precisamos encontrar maneiras de tornar essas coisas mais fáceis. Por favor, fique atento para a Parte 3.



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