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 rid
columna. 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_dblog
comando 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 PAGE
estilo "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 rid
columna:
Ranura 0 Columna 1 Desplazamiento 0x4 Longitud 4 Longitud (física) 4
rid = 1
La última línea anterior, rid = 1
devuelve 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 DELETED
texto donde normalmente estaría el nombre de la columna. El valor de la columna se devuelve NULL
ya 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