АЛЬТЕР ТАБЛИЦІ… ДРОПУВАТИ КОЛІНУ дійсно лише операція з метаданими?


11

Я знайшов декілька джерел, які заявляють НАЗАД ТАБЛИЦІ ... ДРОПОВИЙ КОЛІН - це лише метадані.

Джерело

Як це може бути? Чи не потрібно дані під час DROP COLUMN очищати з основних некластеризованих індексів та кластерних індексів / купи?

Крім того, чому документи "Документи Microsoft" означають, що це операція, повністю зареєстрована?

Зміни, внесені до таблиці, записуються в систему та повністю підлягають відновленню. Зміни, які стосуються всіх рядків у великих таблицях, наприклад, випадання стовпця або додавання стовпця NOT NULL зі значенням за замовчуванням у деяких виданнях SQL Server, можуть зайняти тривалий час для заповнення та генерації багатьох записів журналу . Запустіть ці оператори ALTER TABLE з такою ж уважністю, як і будь-який оператор INSERT, UPDATE або DELETE, який стосується багатьох рядків.

В якості другого питання: як двигун відслідковує відкинуті стовпці, якщо дані не видаляються з базових сторінок?


2
Ну, я вважаю, що мова пережила багато версій продукту та багато інших ітерацій документації. З часом все більше операцій із залученням стовпців стають лише змінами в мережі / метадані. Зараз це, мабуть, поганий конкретний приклад, але мета речення - просто попередити вас, що, як правило, деякі операції з зміни можуть бути певними операціями розміру даних у певних сценаріях, а не перелічувати кожен конкретний сценарій.
Аарон Бертран

Відповіді:


14

Існують певні обставини, коли пропускання стовпця може бути лише операцією з метаданими. Визначення стовпців для будь-якої заданої таблиці не включаються до кожної сторінки, де зберігаються рядки, визначення стовпців зберігаються лише у метаданих бази даних, включаючи 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.