ALTER TABLE ... DROP COLUMN è davvero un'operazione di soli metadati?


11

Ho trovato diverse fonti che affermano ALTER TABLE ... DROP COLUMN è un'operazione di soli metadati.

fonte

Come può essere? Non è necessario eliminare i dati durante una COLONNA DROP dagli indici sottostanti non cluster e dall'indice / heap cluster?

Inoltre, perché Microsoft Docs implica che si tratta di un'operazione completamente registrata?

Le modifiche apportate alla tabella sono registrate e completamente recuperabili. Le modifiche che influiscono su tutte le righe di tabelle di grandi dimensioni, come il rilascio di una colonna o, in alcune edizioni di SQL Server, l'aggiunta di una colonna NOT NULL con un valore predefinito, possono richiedere molto tempo per il completamento e la generazione di numerosi record di registro . Esegui queste istruzioni ALTER TABLE con la stessa cura di qualsiasi istruzione INSERT, UPDATE o DELETE che interessa molte righe.

Come domanda secondaria: come fa il motore a tenere traccia delle colonne eliminate se i dati non vengono rimossi dalle pagine sottostanti?


2
Bene, penso che il linguaggio sia sopravvissuto attraverso molte versioni del prodotto e molte più iterazioni della documentazione. Nel corso del tempo, sempre più operazioni che coinvolgono colonne sono diventate modifiche online / metadati. È forse un cattivo esempio specifico ora, ma lo scopo della frase è semplicemente di avvertirti che, in generale, alcune operazioni di modifica potrebbero essere operazioni di dimensioni dei dati in determinati scenari, piuttosto che elencare ogni singolo scenario specifico.
Aaron Bertrand

Risposte:


14

Ci sono alcune circostanze in cui il rilascio di una colonna può essere un'operazione di soli metadati. Le definizioni di colonna per una determinata tabella non sono incluse in ogni pagina in cui sono archiviate le righe, le definizioni di colonna sono memorizzate solo nei metadati del database, inclusi sys.sysrowsets, sys.sysrscols, ecc.

Quando si elimina una colonna a cui non fa riferimento nessun altro oggetto, il motore di archiviazione contrassegna semplicemente la definizione della colonna come non più presente eliminando i dettagli pertinenti da varie tabelle di sistema. L'azione di eliminare i metadati invalida la cache delle procedure, richiedendo una ricompilazione ogni volta che una query fa successivamente riferimento a tale tabella. Poiché la ricompilazione restituisce solo le colonne attualmente presenti nella tabella, i dettagli della colonna rilasciata non vengono nemmeno richiesti; il motore di archiviazione salta i byte memorizzati in ciascuna pagina per quella colonna, come se la colonna non esistesse più.

Quando si verifica una successiva operazione DML sulla tabella, le pagine interessate vengono riscritte senza i dati per la colonna eliminata. Se si ricostruisce un indice cluster o un heap, tutti i byte per la colonna rilasciata non vengono naturalmente riscritti nella pagina su disco. Questo espande efficacemente il carico di far cadere la colonna nel tempo, rendendola meno evidente.

In alcuni casi non è possibile eliminare una colonna, ad esempio quando la colonna è inclusa in un indice o quando è stato creato manualmente un oggetto statistico per la colonna. Ho scritto un post sul blog che mostra l'errore che viene presentato quando si tenta di modificare una colonna con un oggetto statistico creato manualmente. La stessa semantica si applica quando si rilascia una colonna: se la colonna fa riferimento a qualsiasi altro oggetto, non può semplicemente essere eliminata. L'oggetto di riferimento deve essere prima modificato, quindi è possibile eliminare la colonna.

Questo è abbastanza facile da mostrare guardando il contenuto del registro delle transazioni dopo aver lasciato cadere una colonna. Il codice seguente crea una tabella con una singola colonna di caratteri lunga 8.000. Aggiunge una riga, quindi la rilascia e visualizza il contenuto del registro delle transazioni applicabile all'operazione di eliminazione. I record del registro mostrano le modifiche a varie tabelle di sistema in cui sono memorizzate le definizioni di tabelle e colonne. Se i dati della colonna fossero effettivamente eliminati dalle pagine allocate alla tabella, vedresti i record di registro che registrano i dati della pagina effettiva; non ci sono tali record.

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)

(L'output è troppo grande per essere mostrato qui e dbfiddle.uk non mi permetterà di accedere a fn_dblog)

Il primo set di output mostra il registro come risultato dell'istruzione DDL che rilascia la colonna. Il secondo set di output mostra il registro dopo aver eseguito l'istruzione DML in cui aggiorniamo la ridcolonna. Nel secondo set di risultati, vediamo i record di registro che indicano un'eliminazione rispetto a dbo.DropColumnTest, seguita da un inserimento in dbo.DropColumnTest. Ogni lunghezza del record del registro è 8116, a indicare che la pagina effettiva è stata aggiornata.

Come puoi vedere dall'output del fn_dblogcomando nel test sopra, l'intera operazione è completamente registrata. Questo vale per il recupero semplice, nonché per il recupero completo. La terminologia "completamente registrata" potrebbe essere interpretata erroneamente poiché la modifica dei dati non è stata registrata. Questo non è ciò che accade: la modifica è registrata e può essere completamente ripristinata. Il registro sta semplicemente registrando solo le pagine che sono state toccate e poiché nessuna delle pagine di dati della tabella è stata registrata dall'operazione DDL, sia il DROP COLUMNrollback che l'eventuale verificarsi potrebbero verificarsi in modo estremamente rapido, indipendentemente dalle dimensioni della tabella.

Per la scienza , il seguente codice scaricherà le pagine di dati per la tabella inclusa nel codice sopra, usando lo DBCC PAGEstile "3". Lo stile "3" indica che vogliamo l' intestazione della pagina più un'interpretazione dettagliata per riga . Il codice utilizza un cursore per visualizzare i dettagli di ogni pagina della tabella, quindi potresti voler assicurarti di non eseguirlo su una tabella di grandi dimensioni.

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

Guardando l'output per la prima pagina dalla mia demo (dopo che la colonna è stata eliminata, ma prima che la colonna venga aggiornata), vedo questo:

PAGINA: (1: 100104)


BUFFER:


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                        

INTESTAZIONE DI PAGINA:


Pagina @ 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 
Metadati: AllocUnitId = 72057594057588736                                
Metadati: PartitionId = 72057594051756032 Metadati: IndexId = 1
Metadati: 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 DB Frag ID = 1                      

Stato di allocazione

GAM (1: 2) = ASSEGNATO SGAM (1: 3) = NON ASSEGNATO          
PFS (1: 97056) = 0x40 ASSEGNATO 0_PCT_FULL DIFF (1: 6) = MODIFICATO
ML (1: 7) = NON MIN_LOGGED           

Slot 0 Offset 0x60 Lunghezza 8015

Tipo di record = PRIMARY_RECORD Attributi del record = NULL_BITMAP VARIABLE_COLUMNS
Dimensione record = 8015                  
Memory Dump @ 0x000000B75227A060

0000000000000000: 30000800 01000000 02000001 004f1f5a 5a5a5a5a 0 ............ O.ZZZZZ
0000000000000014: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZ
.
.
.
0000000000001F2C: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F40: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a ZZZZZZZZZZZZZZZ

Slot 0 Colonna 1 Offset 0x4 Lunghezza 4 Lunghezza (fisica) 4

rid = 1                             

Slot 0 Colonna 67108865 Offset 0xf Lunghezza 0 Lunghezza (fisica) 8000

ELIMINATO = NULL                      

Slot 0 Offset 0x0 Lunghezza 0 Lunghezza (fisica) 0

KeyHashValue = (8194443284a0)       

Ho rimosso la maggior parte del dump della pagina non elaborata dall'output mostrato sopra per brevità. Alla fine dell'output, vedrai questo per la ridcolonna:

Slot 0 Colonna 1 Offset 0x4 Lunghezza 4 Lunghezza (fisica) 4

rid = 1                             

L'ultima riga sopra, rid = 1restituisce il nome della colonna e il valore corrente memorizzato nella colonna della pagina.

Quindi vedrai questo:

Slot 0 Colonna 67108865 Offset 0xf Lunghezza 0 Lunghezza (fisica) 8000

ELIMINATO = NULL                      

L'output mostra che lo slot 0 contiene una colonna eliminata, in virtù del DELETEDtesto in cui si trova normalmente il nome della colonna. Il valore della colonna viene restituito come da NULLquando la colonna è stata eliminata. Tuttavia, come puoi vedere nei dati non elaborati, il valore lungo 8000 caratteri REPLICATE('Z', 8000), per quella colonna esiste ancora sulla pagina. Questo è un esempio di quella parte dell'output PAGINA DBCC:

0000000000001EDC: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZ
0000000000001EF0: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F04: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F18: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZ
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.