Існують певні обставини, коли пропускання стовпця може бути лише операцією з метаданими. Визначення стовпців для будь-якої заданої таблиці не включаються до кожної сторінки, де зберігаються рядки, визначення стовпців зберігаються лише у метаданих бази даних, включаючи sys.sysrowsets, sys.sysrscols тощо.
Коли випадає стовпець, на який не посилається жоден інший об'єкт, двигун зберігання просто позначає визначення стовпця як немає, видаляючи відповідні деталі з різних системних таблиць. Дія видалення метаданих приводить до недійсності кешу процедури, вимагаючи перекомпіляції, коли запит згодом посилається на цю таблицю. Оскільки перекомпіляція повертає лише стовпці, які наразі існують у таблиці, деталі стовпців для випавшого стовпця ніколи навіть не запитуються; двигун зберігання пропускає байти, збережені на кожній сторінці для цього стовпця, як ніби стовпець більше не існує.
Коли наступна операція DML відбувається проти таблиці, сторінки, на які впливає, перезаписуються без даних для випавшого стовпця. Якщо ви відновите кластерний індекс або купу, всі байти для стовпця, що випав, природно не записуються на сторінку на диску. Це ефективно поширює навантаження, що скидає колону з часом, роблячи її менш помітною.
Існують обставини, коли ви не можете скинути стовпець, наприклад, коли стовпець включений до індексу або коли ви вручну створили об’єкт статистики для стовпця. Я написав допис у блозі, де відображається помилка, яка подається при спробі змінити стовпець із створеним вручну об’єктом статистики. Ця ж семантика застосовується і при випаді стовпця - якщо на стовпець посилається будь-який інший об'єкт, його не можна просто скинути. Об'єкт посилання спочатку повинен бути змінений, після чого стовпець можна скинути.
Це досить легко показати, переглянувши вміст журналу транзакцій після випадання стовпця. Нижче наведений код створює таблицю з одним 8000 довгим стовпчиком. Він додає рядок, потім скидає його і відображає вміст журналу транзакцій, застосовний до операції падіння. Записи журналу показують модифікації різних системних таблиць, де зберігаються визначення таблиці та стовпців. Якщо дані стовпців фактично видалялися зі сторінок, виділених до таблиці, ви побачите записи журналів, що записують фактичні дані сторінки; таких записів немає.
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)
(Вихід занадто великий, щоб показувати тут, і dbfiddle.uk не дозволить мені отримати доступ до fn_dblog)
Перший набір результатів показує журнал у результаті випадання стовпця оператора DDL. Другий набір результатів показує журнал після запуску оператора DML, де ми оновлюємо rid
стовпець. У другому наборі результатів ми бачимо записи журналів із вказівкою на видалення проти dbo.DropColumnTest, а потім вставку в dbo.DropColumnTest. Кожна довжина запису журналу становить 8116, що вказує, що фактична сторінка була оновлена.
Як ви можете бачити на виході fn_dblog
команди в вищезгаданому тесті, вся операція буде повністю увійшла. Це стосується простого відновлення, а також повного одужання. Термінологія "повністю зареєстрована" може бути неправильно інтерпретована, оскільки модифікація даних не записується. Це не те, що відбувається - модифікація записується в журнал, і її можна повністю повернути назад. Журнал просто записує лише ті сторінки, які торкнулися, і оскільки жодна сторінка даних таблиці не була зареєстрована операцією DDL DROP COLUMN
, і будь-який, і будь-який відкат, який може статися, відбудуться надзвичайно швидко, незалежно від розміру таблиці.
Для науки наступний код буде скидати сторінки даних для таблиці, що міститься у наведеному вище коді, використовуючи DBCC PAGE
стиль "3". Стиль "3" вказує, що нам потрібно заголовок сторінки, а також детальна інтерпретація рядків . У коді використовується курсор для відображення деталей для кожної сторінки таблиці, тому ви можете переконатися, що ви не запускаєте це у великій таблиці.
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);
Дивлячись на вихід першої сторінки з мого демо (після того, як стовпець буде скинуто, але перед оновленням стовпця), я бачу таке:
СТОРІНКА: (1: 100104)
БУФЕР:
BUF @ 0x0000021793E42040
bpage = 0x000002175A7A0000 bhash = 0x0000000000000000 bpageno = (1: 100104)
bdbid = 10 бреферів = 1 bcputicks = 0
bsampleCount = 0 bUse1 = 13760 bstat = 0x10b
blog = 0x212121cc bnext = 0x0000000000000000 bDirtyContext = 0x000002175004B640
bstat2 = 0x0
СТОРІНКА КОРИСТУВАЧА:
Сторінка @ 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
Метадані: AllocUnitId = 72057594057588736
Метадані: PartitionId = 72057594051756032 Метадані: IndexId = 1
Метадані: 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 Ідентифікатор фрагмента БД = 1
Статус розподілу
GAM (1: 2) = ALLOCATED SGAM (1: 3) = НЕ РОЗДАЛЕНО
PFS (1: 97056) = 0x40 ALLOCATED 0_PCT_FULL DIFF (1: 6) = ЗМІНЕНО
ML (1: 7) = НЕ MIN_LOGGED
Слот 0 Зміщення 0x60 Довжина 8015
Тип запису = PRIMARY_RECORD Атрибути запису = NULL_BITMAP VARIABLE_COLUMNS
Розмір запису = 8015
Дамп пам'яті @ 0x000000B75227A060
0000000000000000: 30000800 01000000 02000001 004f1f5a 5a5a5a5a 0 ............ O.ZZZZZ
0000000000000014: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZ
.
.
.
0000000000001F2C: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F40: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a ZZZZZZZZZZZZZZZZ
Слот 0 Стовпчик 1 Зсув 0x4 Довжина 4 Довжина (фізична) 4
позбутися = 1
Слот 0 Колонка 67108865 Зсув 0xf Довжина 0 Довжина (фізична) 8000
DROPPED = NULL
Слот 0 Зсув 0х0 Довжина 0 Довжина (фізична) 0
KeyHashValue = (8194443284a0)
Я видалив більшу частину неочищеного дамп сторінки з результатів, показаних вище для стислості. В кінці виводу ви побачите це для rid
стовпця:
Слот 0 Стовпчик 1 Зсув 0x4 Довжина 4 Довжина (фізична) 4
позбутися = 1
Останній рядок вище, rid = 1
повертає назву стовпця та поточне значення, збережене у стовпці на сторінці.
Далі ви побачите це:
Слот 0 Колонка 67108865 Зсув 0xf Довжина 0 Довжина (фізична) 8000
DROPPED = NULL
Вихід показує, що слот 0 містить вилучений стовпчик, в силу DELETED
тексту, у якому зазвичай стоїть назва стовпця. Значення стовпця повертається NULL
з моменту видалення стовпця. Однак, як ви бачите в необроблених даних, значення 8000 символів REPLICATE('Z', 8000)
для цього стовпця все ще існує на сторінці. Це зразок тієї частини вихідної сторінки DBCC PAGE:
0000000000001EDC: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001EF0: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F04: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZ
0000000000001F18: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZ