Alterando uma coluna de NOT NULL para NULL - O que está acontecendo sob o capô?

25

Temos uma tabela com 2.3B linhas. Gostaríamos de alterar uma coluna de NOT NULL para NULL. A coluna está contida em um índice (não no índice clusterizado ou PK). O tipo de dados não está mudando (é um INT). Apenas a nulidade. A declaração é a seguinte:

Alter Table dbo.Workflow Alter Column LineId Int NULL

A operação demora mais de 10 antes de pará-la (ainda nem a concluímos porque é uma operação de bloqueio e está demorando muito). Provavelmente copiaremos a tabela para um servidor de desenvolvimento e testaremos quanto tempo realmente leva. Mas estou curioso para saber se alguém sabe o que o SQL Server está fazendo ao converter de NOT NULL para NULL? Além disso, os índices afetados precisam ser reconstruídos? O plano de consulta gerado não indica o que está acontecendo.

A tabela em questão está agrupada (não é uma pilha).

Randy Minder
fonte
2
Eu acho que teria que atualizar o bitmap nulo em todas as páginas de dados no nível da folha. E com 2.3B linhas, aposto que teria muitas páginas para trabalhar. Não tenho muita certeza disso.
souplex
3
Também pode estar ocupado colocando um bitmap nulo no índice. O bitmap NULL NÃO estará presente em um ÍNDICE NÃO CLUSTERED se todas as colunas da definição de índice estiverem definidas como NOT NULL.
souplex

Respostas:

27

Como mencionado por @Souplex nos comentários, uma explicação possível pode ser se essa coluna for a primeira NULLcoluna do índice não agrupado em que ela participa.

Para a seguinte configuração

CREATE TABLE Foo
  (
     A UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
     B CHAR(1) NOT NULL DEFAULT 'B'
  )

CREATE NONCLUSTERED INDEX ix
  ON Foo(B);

INSERT INTO Foo
            (B)
SELECT TOP 100000 'B'
FROM   master..spt_values v1,
       master..spt_values v2 

sys.dm_db_index_physical_stats mostra que o índice não agrupado ixpossui 248 páginas em folha e uma única página raiz.

Uma linha típica em uma página folha de índice é semelhante a

insira a descrição da imagem aqui

E na página raiz

insira a descrição da imagem aqui

Então correndo ...

CHECKPOINT;

GO

ALTER TABLE Foo ALTER COLUMN B  CHAR(1) NULL;


SELECT Operation, 
       Context,
       ROUND(SUM([Log Record Length]) / 1024.0,1) AS [Log KB],
       COUNT(*) as [OperationCount]
FROM sys.fn_dblog(NULL,NULL)
WHERE AllocUnitName = 'dbo.Foo.ix'
GROUP BY Operation, Context

Devolvida

+-----------------+--------------------+-------------+----------------+
|    Operation    |      Context       |   Log KB    | OperationCount |
+-----------------+--------------------+-------------+----------------+
| LOP_SET_BITS    | LCX_GAM            | 4.200000    |             69 |
| LOP_FORMAT_PAGE | LCX_IAM            | 0.100000    |              1 |
| LOP_SET_BITS    | LCX_IAM            | 4.200000    |             69 |
| LOP_FORMAT_PAGE | LCX_INDEX_INTERIOR | 8.700000    |              3 |
| LOP_FORMAT_PAGE | LCX_INDEX_LEAF     | 2296.200000 |            285 |
| LOP_MODIFY_ROW  | LCX_PFS            | 16.300000   |            189 |
+-----------------+--------------------+-------------+----------------+

Verificando novamente a folha de índice, as linhas agora parecem

insira a descrição da imagem aqui

e as linhas nas páginas de nível superior, como abaixo.

insira a descrição da imagem aqui

Cada linha foi atualizada e agora contém dois bytes para a contagem de colunas, além de outro byte para o NULL_BITMAP.

Devido à largura extra da linha, o índice não agrupado agora tem 285 páginas de folha e agora duas páginas de nível intermediário junto com a página raiz.

O plano de execução para o

 ALTER TABLE Foo ALTER COLUMN B  CHAR(1) NULL;

tem a seguinte aparência

insira a descrição da imagem aqui

Isso cria uma cópia totalmente nova do índice, em vez de atualizar a existente e precisar dividir as páginas.

Martin Smith
fonte
9

Definitivamente, recriará o índice não clusterizado e não apenas atualizará os metadados. Isso é testado no SQL 2014 e realmente não deve ser testado em um sistema de produção:

CREATE TABLE [z](
    [a] [int] IDENTITY(1,1) NOT NULL,
    [b] [int] NOT NULL,
 CONSTRAINT [c_a] PRIMARY KEY CLUSTERED  ([a] ASC))
go
CREATE NONCLUSTERED INDEX [nc_b] on z (b asc)
GO
insert into z (b)
values (1);

E agora a parte divertida:

DBCC IND (0, z, -1)

Isso nos dará as páginas do banco de dados em que a tabela e o índice não clusterizado estão armazenados.

Encontre PagePIDonde IndexIDé 2 e PageTypeé 2 e faça o seguinte:

DBCC TRACEON(3604) --are you sure that you are allowed to do this?

e depois:

dbcc page (0, 1, PagePID, 3) with tableresults

Observe que há um bitmap nulo no cabeçalho:

Extração de cabeçalho da página

Agora vamos fazer:

alter table z alter Column b int null;

Se você estiver realmente impaciente, tente executar o dbcc pagecomando novamente, mas ele falhará, então vamos verificar a alocação novamente comDBCC IND (0, z, -1) . A página será movida como se por mágica.

Portanto, alterar a nulidade de uma coluna afetará o armazenamento de índices não agrupados em cluster que cobrem essa coluna, pois os metadados precisam ser atualizados e você não precisará reconstruir os índices posteriormente.


Muitas ALTER TABLE ... ALTER COLUMN ...operações podem ser executadas a ONLINEpartir do SQL Server 2016, mas:

ALTER TABLE (Transact-SQL)

  • A alteração de uma coluna de NOT NULLpara NULLnão é suportada como uma operação online quando a coluna alterada é referenciada por índices não clusterizados.
Spörri
fonte