¿ALTER TABLE ... DROP COLUMN es realmente una operación de metadatos solamente?

11

He encontrado varias fuentes que dicen ALTER TABLE ... DROP COLUMN es una operación solo de metadatos.

Fuente

¿Cómo puede ser esto? ¿Los datos durante una COLUMNA DE GOTA no necesitan purgarse de los índices no agrupados subyacentes y del índice / montón agrupado?

Además, ¿por qué los documentos de Microsoft implican que es una operación completamente registrada?

Las modificaciones realizadas en la tabla se registran y son completamente recuperables. Los cambios que afectan a todas las filas en tablas grandes, como soltar una columna o, en algunas ediciones de SQL Server, agregar una columna NOT NULL con un valor predeterminado, pueden tardar mucho tiempo en completarse y generar muchos registros . Ejecute estas instrucciones ALTER TABLE con el mismo cuidado que cualquier instrucción INSERT, UPDATE o DELETE que afecte a muchas filas.

Como pregunta secundaria: ¿cómo realiza el motor el seguimiento de las columnas caídas si los datos no se eliminan de las páginas subyacentes?

George.Palacios
fuente
2
Bueno, creo que el lenguaje ha sobrevivido a través de muchas versiones del producto y muchas más iteraciones de la documentación. Con el tiempo, más y más operaciones que involucran columnas se han convertido en cambios en línea / metadatos solamente. Tal vez sea un mal ejemplo específico ahora, pero el propósito de la oración es simplemente advertirle que, en general, algunas operaciones alternativas pueden ser operaciones de tamaño de datos en ciertos escenarios, en lugar de enumerar cada escenario específico.
Aaron Bertrand

Respuestas:

14

Hay ciertas circunstancias en las que soltar una columna puede ser una operación de metadatos solamente. Las definiciones de columna para cualquier tabla dada no se incluyen en todas y cada una de las páginas donde se almacenan las filas, las definiciones de columna solo se almacenan en los metadatos de la base de datos, incluidos sys.sysrowsets, sys.sysrscols, etc.

Al soltar una columna a la que ningún otro objeto hace referencia, el motor de almacenamiento simplemente marca la definición de la columna como que ya no está presente al eliminar los detalles pertinentes de varias tablas del sistema. La acción de eliminar los metadatos invalida el caché del procedimiento, lo que requiere una nueva compilación cada vez que una consulta hace referencia a esa tabla. Dado que la recompilación solo devuelve columnas que existen actualmente en la tabla, los detalles de la columna eliminada nunca se solicitan; el motor de almacenamiento omite los bytes almacenados en cada página para esa columna, como si la columna ya no existiera.

Cuando se produce una operación DML posterior en la tabla, las páginas afectadas se vuelven a escribir sin los datos de la columna descartada. Si reconstruye un índice agrupado o un montón, naturalmente, todos los bytes de la columna descartada no se vuelven a escribir en la página del disco. Esto efectivamente extiende la carga de soltar la columna con el tiempo, haciéndola menos notable.

Hay circunstancias en las que no puede soltar una columna, como cuando la columna se incluye en un índice o cuando ha creado manualmente un objeto de estadísticas para la columna. Escribí una publicación de blog que muestra el error que se presenta al intentar alterar una columna con un objeto de estadísticas creado manualmente. La misma semántica se aplica cuando se suelta una columna: si la columna es referenciada por cualquier otro objeto, simplemente no se puede soltar. El objeto de referencia debe modificarse primero, luego la columna puede descartarse.

Esto es bastante fácil de mostrar al mirar el contenido del registro de transacciones después de soltar una columna. El siguiente código crea una tabla con una sola columna de caracteres largos de 8,000. Agrega una fila, luego la descarta y muestra el contenido del registro de transacciones aplicable a la operación de descarte. Los registros de anotaciones muestran modificaciones en varias tablas del sistema donde se almacenan las definiciones de tabla y columna. Si los datos de la columna realmente se eliminaran de las páginas asignadas a la tabla, vería registros que registran los datos reales de la página; No hay tales 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)

(El resultado es demasiado grande para mostrar aquí, y dbfiddle.uk no me permite acceder a fn_dblog)

El primer conjunto de resultados muestra el registro como resultado de que la instrucción DDL descarte la columna. El segundo conjunto de resultados muestra el registro después de ejecutar la instrucción DML donde actualizamos la ridcolumna. En el segundo conjunto de resultados, vemos registros que indican una eliminación de dbo.DropColumnTest, seguido de una inserción en dbo.DropColumnTest. La longitud de cada registro es 8116, lo que indica que se actualizó la página real.

Como puede ver en la salida del fn_dblogcomando en la prueba anterior, toda la operación está completamente registrada. Esto se aplica a la recuperación simple, así como a la recuperación completa. La terminología "totalmente registrada" puede malinterpretarse ya que la modificación de datos no se registra. Esto no es lo que sucede: la modificación se registra y se puede revertir por completo. El registro es simplemente única grabando las páginas que fueron tocados, y puesto que ninguno de los datos en las páginas de la tabla se registra por la operación DDL, tanto el DROP COLUMN, y cualquier reversión que pudiera ocurrir ocurrirá de forma extremadamente rápida, sin importar el tamaño de la tabla.

Para ciencia , el siguiente código volcará las páginas de datos para la tabla incluida en el código anterior, usando el DBCC PAGEestilo "3". El estilo "3" indica que queremos el encabezado de página más la interpretación detallada por fila . El código usa un cursor para mostrar los detalles de cada página de la tabla, por lo que es posible que desee asegurarse de no ejecutar esto en una tabla 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);

Mirando el resultado de la primera página de mi demo (después de que se suelta la columna, pero antes de que la columna se actualice), veo esto:

PÁGINA: (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                        

ENCABEZADO DE PÁGINA:


Página @ 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 
Metadatos: AllocUnitId = 72057594057588736                                
Metadatos: PartitionId = 72057594051756032 Metadatos: IndexId = 1
Metadatos: 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 Fragmento de DB = 1                      

Estado de asignación

GAM (1: 2) = SGAM ASIGNADO (1: 3) = NO ASIGNADO          
PFS (1: 97056) = 0x40 ASIGNADO 0_PCT_FULL DIFF (1: 6) = CAMBIADO
ML (1: 7) = NO MIN_LOGGED           

Ranura 0 Offset 0x60 Longitud 8015

Tipo de registro = PRIMARY_RECORD Atributos de registro = NULL_BITMAP VARIABLE_COLUMNS
Tamaño de registro = 8015                  
Volcado de memoria @ 0x000000B75227A060

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

Ranura 0 Columna 1 Desplazamiento 0x4 Longitud 4 Longitud (física) 4

rid = 1                             

Ranura 0 Columna 67108865 Desplazamiento 0xf Longitud 0 Longitud (física) 8000

CAÍDO = NULO                      

Ranura 0 Offset 0x0 Longitud 0 Longitud (física) 0

KeyHashValue = (8194443284a0)       

He eliminado la mayor parte del volcado de página sin procesar del resultado que se muestra arriba por brevedad. Al final de la salida, verá esto para la ridcolumna:

Ranura 0 Columna 1 Desplazamiento 0x4 Longitud 4 Longitud (física) 4

rid = 1                             

La última línea anterior, rid = 1devuelve el nombre de la columna y el valor actual almacenado en la columna de la página.

A continuación, verás esto:

Ranura 0 Columna 67108865 Desplazamiento 0xf Longitud 0 Longitud (física) 8000

CAÍDO = NULO                      

El resultado muestra que la ranura 0 contiene una columna eliminada, en virtud del DELETEDtexto donde normalmente estaría el nombre de la columna. El valor de la columna se devuelve NULLya que la columna se ha eliminado. Sin embargo, como puede ver en los datos sin procesar, el valor largo de 8,000 caracteres REPLICATE('Z', 8000)para esa columna todavía existe en la página. Esta es una muestra de esa parte de la salida de DBCC PAGE:

0000000000001EDC: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
0000000000001EF0: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
0000000000001F04: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
0000000000001F18: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZ
Max Vernon
fuente