Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Novas alterações de coluna somente de metadados no SQL Server 2016


A ALTER TABLE ... ALTER COLUMN comando é muito poderoso. Você pode usá-lo para alterar o tipo de dados de uma coluna, comprimento, precisão, escala, nulidade, agrupamento… e muitas outras coisas além.

Certamente é mais conveniente que a alternativa:criar uma nova tabela e migrar os dados sempre que uma alteração for necessária. No entanto, há muito que pode ser feito para esconder a complexidade subjacente. Junto com um grande número de restrições sobre o que é possível com este comando, há sempre a questão do desempenho.

Em última análise, as tabelas são armazenadas como uma sequência de bytes com alguns metadados em outras partes do sistema para descrever o que cada um desses bytes significa e como eles se relacionam com cada uma das várias colunas da tabela. Quando pedimos ao SQL Server para alterar algum aspecto da definição de uma coluna, ele precisa verificar se os dados existentes são compatíveis com a nova definição. Ele também precisa determinar se o layout físico atual precisa ser alterado.

Dependendo do tipo de alteração e da configuração do banco de dados, um ALTER COLUMN comando precisará executar uma das seguintes ações:
  1. Altere os metadados apenas nas tabelas do sistema.
  2. Verifique a compatibilidade de todos os dados existentes e altere os metadados.
  3. Reescreva alguns ou todos os dados armazenados para corresponder à nova definição.

A opção 1 representa o caso ideal do ponto de vista do desempenho. Requer apenas algumas alterações nas tabelas do sistema e uma quantidade mínima de log. A operação ainda exigirá uma modificação de esquema restritiva Sch-M lock, mas as próprias alterações de metadados serão concluídas muito rapidamente, independentemente do tamanho da tabela.

Alterações somente de metadados


Há vários casos especiais a serem observados, mas como resumo geral, as ações a seguir exigem apenas alterações nos metadados:
  • Indo de NOT NULL para NULL para o mesmo tipo de dados.
  • Aumentar o tamanho máximo de um varchar , nvarchar , ou varbinary coluna (exceto para max ).

Melhorias no SQL Server 2016


O assunto desta postagem são as alterações adicionais habilitadas apenas para metadados do SQL Server 2016 em diante . Nenhuma alteração na sintaxe é necessária e nenhuma configuração precisa ser modificada. Você obtém essas melhorias não documentadas gratuitamente.



Os novos recursos visam um subconjunto de comprimento fixo tipos de dados. As novas habilidades se aplicam a tabelas de armazenamento de linha nas seguintes circunstâncias:
  • A compactação deve estar ativada:
    • Em todos os índices e partições , incluindo o heap base ou o índice clusterizado.
    • Qualquer ROW ou PAGE compressão.
    • Índices e partições podem usar uma mistura desses níveis de compressão. O importante é que não haja partições ou índices descompactados.
  • Mudando de NULL para NOT NULL é não permitido .
  • As seguintes alterações de tipo inteiro são suportados:
    • smallint para integer ou bigint .
    • integer para bigint .
    • smallmoney para money (usa representação inteira internamente).
  • As seguintes alterações de tipo de string e binário são suportados:
    • char(n) para char(m) ou varchar(m)
    • nchar(n) para nchar(m) ou nvarchar(m)
    • binary(n) para binary(m) ou varbinary(m)
    • Todos os itens acima apenas para n < m e m != max
    • As alterações de agrupamento não são permitidas

Essas alterações podem ser apenas de metadados porque o layout de dados binários subjacente não muda quando o Descritor de coluna formato de linha é usado (daí a necessidade de compactação). Sem compactação, o armazenamento de linha usa a FixedVar original representação, que não pode acomodar essas alterações de tipo de dados de comprimento fixo sem reescrever o layout físico.

Você pode notar que tinyint é omitido da lista de tipos inteiros. Isso ocorre porque ele não é assinado, enquanto os outros tipos inteiros são todos assinados, portanto, uma alteração somente de metadados não é possível. Por exemplo, um valor de 255 pode caber em um byte para tinyint , mas requer dois bytes em qualquer um dos formatos assinados. Os formatos assinados podem conter -128 a +127 em um byte quando compactados.

Exemplo de inteiro


Uma aplicação muito útil dessa melhoria é alterar o tipo de dados de uma coluna com o IDENTITY propriedade.

Digamos que temos a seguinte tabela de heap usando compactação de linha (a compactação de página também funcionaria):
DROP TABLE IF EXISTS dbo.Test;
GO
CREATE TABLE dbo.Test
(
    id integer IDENTITY NOT NULL,
    some_value integer NOT NULL
)
WITH (DATA_COMPRESSION = ROW);

Vamos adicionar 5 milhões de linhas de dados. Isso será suficiente para tornar óbvio (do ponto de vista do desempenho) se a alteração do tipo de dados da coluna é uma operação somente de metadados ou não:
WITH Numbers AS
(
    SELECT 
        n = ROW_NUMBER() OVER (ORDER BY @@SPID) 
    FROM sys.all_columns AS AC1
    CROSS JOIN sys.all_columns AS AC2
    ORDER BY n
    OFFSET 0 ROWS
    FETCH FIRST 5 * 1000 * 1000 ROWS ONLY
)
INSERT dbo.Test
    WITH (TABLOCKX)
(
    some_value
)
SELECT
    N.n
FROM Numbers AS N;

Em seguida, vamos propagar novamente a IDENTITY para fazer parecer que estamos quase no ponto de esgotar os valores que cabem em um integer :
DBCC CHECKIDENT
(
    N'dbo.Test',
    RESEED,
    2147483646
);

Podemos adicionar mais uma linha com sucesso:
INSERT dbo.Test
    (some_value)
VALUES
    (123456);

Mas tentando adicionar outra linha:
INSERT dbo.Test
    (some_value)
VALUES
    (7890);

Resulta em uma mensagem de erro:
Msg 8115, Level 16, State 1, Line 1
Erro de estouro aritmético convertendo IDENTITY para tipo de dados int.
Podemos corrigir isso convertendo a coluna para bigint :
ALTER TABLE dbo.Test
ALTER COLUMN id bigint NOT NULL;

Graças às melhorias no SQL Server 2016, este comando altera somente metadados , e conclui imediatamente. O INSERT anterior instrução (a que gerou o erro de estouro aritmético) agora é concluída com êxito.

Essa nova capacidade não resolve todos os problemas relacionados à alteração do tipo de uma coluna com a IDENTITY propriedade. Ainda precisaremos descartar e recriar quaisquer índices na coluna, recriar quaisquer chaves estrangeiras de referência e assim por diante. Isso está um pouco fora do escopo deste post (embora Aaron Bertrand tenha escrito sobre isso antes). Ser capaz de alterar o tipo como uma operação somente de metadados certamente não faz mal. Com um planejamento cuidadoso, as outras etapas necessárias podem ser feitas da forma mais eficiente possível, por exemplo, usando registro mínimo ou ONLINE operações.

Tenha cuidado com a sintaxe


Certifique-se de sempre especifique NULL ou NOT NULL ao alterar os tipos de dados com ALTER COLUMN . Digamos, por exemplo, que queremos também alterar o tipo de dados do some_value coluna em nossa tabela de teste de integer NOT NULL para bigint NOT NULL .

Quando escrevemos o comando, omitimos o NULL ou NOT NULL qualificador:
ALTER TABLE dbo.Test
ALTER COLUMN some_value bigint;

Este comando é concluído com sucesso como uma alteração somente de metadados, mas também remove o NOT NULL limitação. A coluna agora é bigint NULL , que não é o que pretendíamos. Esse comportamento é documentado, mas é fácil ignorar.

Podemos tentar corrigir nosso erro com:
ALTER TABLE dbo.Test
ALTER COLUMN some_value bigint NOT NULL;

Isso não uma alteração somente de metadados. Não temos permissão para mudar de NULL para NOT NULL (consulte a tabela anterior se precisar de uma atualização sobre as condições). O SQL Server precisará verificar todos os valores existentes para garantir que nenhum nulo esteja presente. Ele então reescreverá fisicamente cada linha da mesa. Além de serem lentas em si, essas ações geram uma grande quantidade de log de transações, que podem ter efeitos indiretos.

Como observação lateral, esse mesmo erro não é possível para colunas com a IDENTITY propriedade. Se escrevermos uma ALTER COLUMN declaração sem NULL ou NOT NULL nesse caso, o mecanismo presume que queremos dizer NOT NULL porque a propriedade identity não é permitida em colunas anuláveis. Ainda é uma ótima ideia não confiar nesse comportamento.

Sempre especifique NULL ou NOT NULL com ALTER COLUMN .

Agrupamento


É necessário um cuidado especial ao alterar uma coluna de string que tenha um agrupamento que não corresponda ao padrão do banco de dados.

Por exemplo, digamos que temos uma tabela com um agrupamento que diferencia maiúsculas de minúsculas e acentos (suponha que o padrão do banco de dados seja diferente):
DROP TABLE IF EXISTS dbo.Test2;
GO
CREATE TABLE dbo.Test2
(
    id integer IDENTITY NOT NULL,
    some_string char(8) COLLATE Latin1_General_100_CS_AS NOT NULL
)
WITH (DATA_COMPRESSION = ROW);

Adicione 5 milhões de linhas de dados:
WITH Numbers AS
(
    SELECT 
        n = ROW_NUMBER() OVER (ORDER BY @@SPID) 
    FROM sys.all_columns AS AC1
    CROSS JOIN sys.all_columns AS AC2
    ORDER BY n
    OFFSET 0 ROWS
    FETCH FIRST 5 * 1000 * 1000 ROWS ONLY
)
INSERT dbo.Test2
    WITH (TABLOCKX)
(
    some_string
)
SELECT
    CONVERT(char(8), N.n) COLLATE Latin1_General_100_CS_AS
FROM Numbers AS N;

Dobre o comprimento da coluna de string usando o seguinte comando:
ALTER TABLE dbo.Test2
ALTER COLUMN some_string char(16) NOT NULL;

Lembramos de especificar NOT NULL , mas esqueceu o agrupamento não padrão. O SQL Server assume que pretendemos alterar o agrupamento para o padrão do banco de dados (Latin1_General_CI_AS para meu banco de dados de teste). Alterar o agrupamento impede que a operação seja somente de metadados e, portanto, a operação é executada por vários minutos, gerando pilhas de log.

Recrie a tabela e os dados usando o script anterior e tente ALTER COLUMN command novamente, mas especificando o agrupamento não padrão existente como parte do comando:
ALTER TABLE dbo.Test2
ALTER COLUMN some_string 
    char(16) COLLATE Latin1_General_100_CS_AS NOT NULL;

A alteração agora é concluída imediatamente, como uma operação somente de metadados. Assim como o NULL e NOT NULL sintaxe, vale a pena ser explícito para evitar acidentes. Este é um bom conselho em geral, não apenas para ALTER COLUMN .

Compressão


Esteja ciente de que a compactação precisa ser especificada explicitamente para cada índice e separadamente para a tabela base, se for um heap. Este é outro exemplo em que o uso de sintaxe ou atalhos abreviados pode impedir o resultado desejado.

Por exemplo, a tabela a seguir não especifica a compactação explícita para a chave primária ou a definição de índice in-line:
CREATE TABLE dbo.Test
(
    id integer IDENTITY NOT NULL PRIMARY KEY,
    some_value integer NOT NULL
        INDEX [IX dbo.Test some_value]
)
WITH (DATA_COMPRESSION = PAGE);

A PRIMARY KEY terá um nome atribuído, padrão para CLUSTERED , e seja PAGE comprimido. O índice in-line será NONCLUSTERED e não compactado. Esta tabela não será habilitada para nenhuma das novas otimizações porque nem todos os índices e partições são compactados.

Uma definição de tabela muito melhor e mais explícita seria:
CREATE TABLE dbo.Test
(
    id integer IDENTITY NOT NULL
        CONSTRAINT [PK dbo.Test id]
        PRIMARY KEY CLUSTERED
        WITH (DATA_COMPRESSION = PAGE),
    some_value integer NOT NULL
        INDEX [IX dbo.Test some_value]
        NONCLUSTERED
        WITH (DATA_COMPRESSION = ROW)        
);

Essa tabela se qualificará para as novas otimizações porque todos os índices e partições são compactados. Como observado anteriormente, misturar tipos de compressão é bom.

Existem várias maneiras de escrever este CREATE TABLE declaração de forma explícita, então há um elemento de preferência pessoal. O ponto importante é sempre ser explícito sobre o que você quer. Isso se aplica a separar CREATE INDEX declarações também.

Eventos estendidos e sinalizador de rastreamento


Há um evento estendido especificamente para os novos metadados ALTER COLUMN operações com suporte no SQL Server 2016 em diante.

O evento estendido é compressed_alter_column_is_md_only em Depurar canal. Seus campos de evento são object_id , column_id e is_md_only (verdadeiro falso).

Este evento indica apenas se uma operação é somente metadados devido aos novos recursos do SQL Server 2016. As alterações de coluna que eram somente metadados antes de 2016 mostrarão is_md_only = false apesar de ainda ser apenas metadados.

Outros eventos estendidos úteis para rastrear ALTER COLUMN as operações incluem metadata_ddl_alter_column e alter_column_event , ambos no Analytic canal.

Se você precisar desativar os novos recursos do SQL Server 2016 por qualquer motivo, o sinalizador de rastreamento global (ou de inicialização) não documentado 3618 pode ser usado. Esse sinalizador de rastreamento não é eficaz quando usado no nível da sessão. Não há como especificar um sinalizador de rastreamento em nível de consulta com um ALTER COLUMN comando.

Considerações finais


Ser capaz de alterar alguns tipos de dados inteiros de comprimento fixo com uma alteração somente de metadados é uma melhoria de produto muito bem-vinda. Requer que a tabela já esteja totalmente compactada, mas isso está se tornando mais comum de qualquer maneira. Isso é especialmente verdadeiro porque a compactação foi habilitada em todas as edições a partir do SQL Server 2016 Service Pack 1.

Colunas do tipo string de comprimento fixo são provavelmente muito menos comuns. Parte disso pode ser devido a considerações um tanto desatualizadas, como o uso do espaço. Quando compactadas, as colunas de string de comprimento fixo não armazenam espaços em branco à direita, tornando-as tão eficientes quanto as colunas de string de comprimento variável do ponto de vista de armazenamento. Pode ser irritante cortar espaços para manipulação ou exibição, mas se os dados geralmente ocupam a maior parte do comprimento máximo, os tipos de comprimento fixo podem ter vantagens importantes, principalmente em relação às concessões de memória para coisas como classificação e hash.

Nem tudo são boas notícias com a compactação ativada. Mencionei anteriormente que o SQL Server às vezes pode realizar uma alteração somente de metadados após verificar se todos os valores existentes serão convertidos com êxito para o novo tipo. Este é o caso ao usar ALTER COLUMN para mudar de integer para smallint por exemplo. Infelizmente, no momento, essas operações não são apenas metadados para objetos compactados.

Agradecimentos


Agradecimentos especiais a Panagiotis Antonopoulos (Engenheiro de software principal) e Mirek Sztajno (Sênior Program Manager) da equipe de produto do SQL Server por sua assistência e orientação durante a pesquisa e redação deste artigo.

Nenhum dos detalhes fornecidos neste trabalho deve ser considerado como documentação oficial da Microsoft ou declarações de produtos.