Há certas circunstâncias em que largar uma coluna pode ser uma operação apenas de metadados. As definições de coluna para qualquer tabela não são incluídas em todas as páginas em que as linhas são armazenadas, as definições de coluna são armazenadas apenas nos metadados do banco de dados, incluindo sys.sysrowsets, sys.sysrscols, etc.
Ao eliminar uma coluna que não é referenciada por nenhum outro objeto, o mecanismo de armazenamento simplesmente marca a definição da coluna como não mais presente, excluindo os detalhes pertinentes de várias tabelas do sistema. A ação de excluir os metadados invalida o cache do procedimento, exigindo uma recompilação sempre que uma consulta fizer referência posteriormente a essa tabela. Como a recompilação retorna apenas colunas que existem atualmente na tabela, os detalhes da coluna eliminada nunca são solicitados; o mecanismo de armazenamento ignora os bytes armazenados em cada página dessa coluna, como se a coluna não existisse mais.
Quando uma operação DML subsequente ocorre na tabela, as páginas afetadas são reescritas sem os dados da coluna descartada. Se você recriar um índice em cluster ou um heap, todos os bytes da coluna descartada naturalmente não serão gravados de volta na página no disco. Isso efetivamente espalha a carga de soltar a coluna ao longo do tempo, tornando-a menos perceptível.
Há circunstâncias em que você não pode soltar uma coluna, como quando a coluna é incluída em um índice ou quando você criou manualmente um objeto de estatística para a coluna. Eu escrevi uma postagem de blog mostrando o erro que é apresentado ao tentar alterar uma coluna com um objeto de estatísticas criado manualmente. A mesma semântica se aplica ao descartar uma coluna - se a coluna for referenciada por qualquer outro objeto, ela não poderá ser simplesmente descartada. O objeto de referência deve ser alterado primeiro e, em seguida, a coluna pode ser descartada.
Isso é bastante fácil de mostrar, observando o conteúdo do log de transações depois de soltar uma coluna. O código abaixo cria uma tabela com uma única coluna de caracteres de 8.000 caracteres. Ele adiciona uma linha, a elimina e exibe o conteúdo do log de transações aplicável à operação de descarte. Os registros de log mostram modificações em várias tabelas do sistema em que as definições de tabela e coluna são armazenadas. Se os dados da coluna estivessem realmente sendo excluídos das páginas alocadas à tabela, você veria registros de log registrando os dados reais da página; não existem tais registros.
DROP TABLE IF EXISTS dbo.DropColumnTest;
GO
CREATE TABLE dbo.DropColumnTest
(
rid int NOT NULL
CONSTRAINT DropColumnTest_pkc
PRIMARY KEY CLUSTERED
, someCol varchar(8000) NOT NULL
);
INSERT INTO dbo.DropColumnTest (rid, someCol)
SELECT 1, REPLICATE('Z', 8000);
GO
DECLARE @startLSN nvarchar(25);
SELECT TOP(1) @startLSN = dl.[Current LSN]
FROM sys.fn_dblog(NULL, NULL) dl
ORDER BY dl.[Current LSN] DESC;
DECLARE @a int = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10), LEFT(@startLSN, 8), 0), 1)
, @b int = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10), SUBSTRING(@startLSN, 10, 8), 0), 1)
, @c int = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10), RIGHT(@startLSN, 4), 0), 1);
SELECT @startLSN = CONVERT(varchar(8), @a, 1)
+ ':' + CONVERT(varchar(8), @b, 1)
+ ':' + CONVERT(varchar(8), @c, 1)
ALTER TABLE dbo.DropColumnTest DROP COLUMN someCol;
SELECT *
FROM sys.fn_dblog(@startLSN, NULL)
--modify an existing data row
SELECT TOP(1) @startLSN = dl.[Current LSN]
FROM sys.fn_dblog(NULL, NULL) dl
ORDER BY dl.[Current LSN] DESC;
SET @a = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10), LEFT(@startLSN, 8), 0), 1);
SET @b = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10), SUBSTRING(@startLSN, 10, 8), 0), 1);
SET @c = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10), RIGHT(@startLSN, 4), 0), 1);
SELECT @startLSN = CONVERT(varchar(8), @a, 1)
+ ':' + CONVERT(varchar(8), @b, 1)
+ ':' + CONVERT(varchar(8), @c, 1)
UPDATE dbo.DropColumnTest SET rid = 2;
SELECT *
FROM sys.fn_dblog(@startLSN, NULL)
(A saída é grande demais para ser mostrada aqui e o dbfiddle.uk não permitirá que eu acesse fn_dblog)
O primeiro conjunto de saída mostra o log como resultado da instrução DDL descartando a coluna. O segundo conjunto de saída mostra o log após a execução da instrução DML onde atualizamos a rid
coluna. No segundo conjunto de resultados, vemos registros de log indicando uma exclusão no dbo.DropColumnTest, seguido de uma inserção no dbo.DropColumnTest. Cada comprimento do registro de log é 8116, indicando que a página real foi atualizada.
Como você pode ver na saída do fn_dblog
comando no teste acima, toda a operação é totalmente registrada. Isso vale para a recuperação simples, bem como a recuperação completa. A terminologia "totalmente registrada" pode ser mal interpretada, pois a modificação dos dados não é registrada. Não é isso que acontece - a modificação é registrada e pode ser totalmente revertida. O log está simplesmente registrando apenas as páginas que foram tocadas e, como nenhuma das páginas de dados da tabela foi registrada pela operação DDL, a DROP COLUMN
reversão e qualquer reversão que possa ocorrer ocorrerão extremamente rapidamente, independentemente do tamanho da tabela.
Para fins científicos , o código a seguir irá despejar as páginas de dados da tabela incluída no código acima, usando o DBCC PAGE
estilo "3". O estilo "3" indica que queremos o cabeçalho da página mais uma interpretação detalhada por linha . O código usa um cursor para exibir os detalhes de todas as páginas da tabela; portanto, convém não executar isso em uma tabela grande.
DBCC TRACEON(3604); --directs out from DBCC commands to the console, instead of the error log
DECLARE @dbid int = DB_ID();
DECLARE @fileid int;
DECLARE @pageid int;
DECLARE cur CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY
FOR
SELECT dpa.allocated_page_file_id
, dpa.allocated_page_page_id
FROM sys.schemas s
INNER JOIN sys.objects o ON o.schema_id = s.schema_id
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), o.object_id, NULL, NULL, 'DETAILED') dpa
WHERE o.name = N'DropColumnTest'
AND s.name = N'dbo'
AND dpa.page_type_desc = N'DATA_PAGE';
OPEN cur;
FETCH NEXT FROM cur INTO @fileid, @pageid;
WHILE @@FETCH_STATUS = 0
BEGIN
DBCC PAGE (@dbid, @fileid, @pageid, 3);
FETCH NEXT FROM cur INTO @fileid, @pageid;
END
CLOSE cur;
DEALLOCATE cur;
DBCC TRACEOFF(3604);
Observando a saída da primeira página da minha demonstração (após a queda da coluna, mas antes da atualização da coluna), vejo o seguinte:
PÁGINA: (1: 100104)
AMORTECEDOR:
BUF @ 0x0000021793E42040
bpage = 0x000002175A7A0000 bhash = 0x0000000000000000 bpageno = (1: 100104)
bdbid = 10 breferences = 1 bcputicks = 0
bsampleCount = 0 bUse1 = 13760 bstat = 0x10b
blog = 0x212121cc bnext = 0x0000000000000000 bDirtyContext = 0x000002175004B640
bstat2 = 0x0
CABEÇALHO DA PÁGINA:
Page @ 0x000002175A7A0000
m_pageId = (1: 100104) m_headerVersion = 1 m_type = 1
m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0xc000
m_objId (AllocUnitId.idObj) = 300 m_indexId (AllocUnitId.idInd) = 256
Metadados: AllocUnitId = 72057594057588736
Metadados: PartitionId = 72057594051756032 Metadados: IndexId = 1
Metadados: ObjectId = 174623665 m_prevPage = (0: 0) m_nextPage = (0: 0)
pminlen = 8 m_slotCnt = 1 m_freeCnt = 79
m_freeData = 8111 m_reservedCnt = 0 m_lsn = (616: 14191: 25)
m_xactReserved = 0 m_xdesId = (0: 0) m_ghostRecCnt = 0
m_tornBits = 0 ID de frag do banco de dados = 1
Status de alocação
GAM (1: 2) = SGAM ATRIBUÍDO (1: 3) = NÃO ATRIBUÍDO
PFS (1: 97056) = 0x40 ALOCADO 0_PCT_FULL DIFF (1: 6) = ALTERADO
ML (1: 7) = NÃO MIN_LOGGED
Comprimento do deslocamento 0x60 do slot 0x60 8015
Tipo de registro = PRIMARY_RECORD Atributos do registro = NULL_BITMAP VARIABLE_COLUMNS
Tamanho do registro = 8015
Despejo de memória @ 0x000000B75227A060
0000000000000000: 30000800 01000000 02000001 004f1f5a 5a5a5a5a 0 ............ O.ZZZZZ
0000000000000014: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
.
.
.
0000000000001F2C: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F40: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a ZZZZZZZZZZZZZZZZ
Slot 0 Coluna 1 Deslocamento 0x4 Comprimento 4 Comprimento (físico) 4
rid = 1
Slot 0 Coluna 67108865 Deslocamento 0xf Comprimento 0 Comprimento (físico) 8000
DROPPED = NULL
Slot 0 Deslocamento 0x0 Comprimento 0 Comprimento (físico) 0
KeyHashValue = (8194443284a0)
Eu removi a maior parte do despejo de página bruta da saída mostrada acima por questões de concisão No final da saída, você verá isso na rid
coluna:
Slot 0 Coluna 1 Deslocamento 0x4 Comprimento 4 Comprimento (físico) 4
rid = 1
A última linha acima rid = 1
,, retorna o nome da coluna e o valor atual armazenado na coluna na página.
Em seguida, você verá o seguinte:
Slot 0 Coluna 67108865 Deslocamento 0xf Comprimento 0 Comprimento (físico) 8000
DROPPED = NULL
A saída mostra que o Slot 0 contém uma coluna excluída, em virtude do DELETED
texto onde o nome da coluna normalmente estaria. O valor da coluna é retornado como NULL
desde que a coluna foi excluída. No entanto, como você pode ver nos dados brutos, o valor de 8.000 caracteres REPLICATE('Z', 8000)
, para essa coluna ainda existe na página. Esta é uma amostra dessa parte da saída DBCC PAGE:
0000000000001EDC: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001EF0: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F04: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F18: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ