Автор: Martyn Jones, A Better Fire Alarm Is Still a Fire
В прошлой статье было показано, как включить и отключить задачу очистки фантомных записей (ghost cleanup) и как фантомные записи представлены на странице данных. В этой статье мы переходим к тому, как процесс удаления, включая пометку фантомных записей, регистрируется в журнале транзакций.
Если вы не читали предыдущие статьи, рекомендуется сделать это, поскольку в них вводятся основные понятия и терминология, а также закладывается понимание, развиваемое на протяжении всей серии. Код демонстрации также используется и дополняется для примеров в этой статье:
- Статья 1 – Призраки! – Фантомные записи и процесс очистки фантомов
- Статья 2 – Наблюдение за работой процесса очистки фантомов
- Статья 3 – Дайте фантомам задержаться
- Статья 4 – Фантомы в журнале транзакций
- Статья 5 – Призрачный случай повреждения индекса
- Статья 6 – Ответы на вопросы
Как используется журнал транзакций
Концепции, обсуждавшиеся в предыдущей статье блога, также можно увидеть в журнале транзакций. Логическое удаление строк (путем пометки их как фантомных записей), связанные обновления метаданных выделения и последующее физическое удаление этих строк процессом очистки фантомов регистрируются как отдельные операции в журнале транзакций.
В демонстрационном коде используется недокументированная функция fn_dblog(), которая обеспечивает четкое представление внутренней последовательности событий, выполняемых SQL Server во время операции удаления, чтобы можно было изучить отдельные физические изменения, необходимые для реализации фантомов и последующей очистки.
Ниже приведены некоторые ключевые операции, которые покажет демонстрация:
Ключевые операции журнала транзакций во время удаления:
- LOP_BEGIN_XACT | LCX_NULL
Начинает транзакцию и регистрирует начало единицы работы в журнале транзакций. - LOP_DELETE_ROWS | LCX_MARK_AS_GHOST
Логически удаляет строку, помечая её как фантомную запись для последующей очистки. - LOP_SET_BITS | LCX_PFS
Устанавливает флаги состояния PFS для страницы, указывая, что страница содержит фантомные записи. - LOP_COMMIT_XACT | LCX_NULL
Фиксирует транзакцию и регистрирует, что зарегистрированные изменения завершены и долговечны. - LOP_EXPUNGE_ROWS | LCX_CLUSTERED
Физически удаляет фантомные строки из структуры данных во время очистки.
Демонстрация
Код в этой статье предназначен для использования в тестовой среде, а не в рабочей.
Предварительные требования
Перед запуском кода из этой статьи необходимо создать базу данных (см. «Настройка демонстрации» в «Статье 2 – Наблюдение за работой процесса очистки фантомов»), затем повторно выполнить код, использованный в «Статье 3 – Дайте фантомам задержаться» в разделе «Viewing ghost records on the data page»; это гарантирует, что данные в журнале транзакций находятся в активной части журнала транзакций. Если требуется больше времени для изучения страниц между шагами, рассмотрите возможность отключения процесса очистки фантомов, как описано в «Статье 3 – Дайте призракам задержаться».
Fn_dblog()
Как уже упоминалось, демонстрационный код будет использовать недокументированную табличную функцию fn_dblog(), которая позволяет читать активную часть журнала транзакций.
Функция принимает два параметра, представляющих начальный и конечный LSN. Передача NULL для обоих параметров возвращает все доступные записи из активного журнала транзакций, а не ограничивает вывод конкретным диапазоном LSN.
Результат содержит одну строку для каждой зарегистрированной операции и включает такую информацию, как:
- Transaction ID – Идентифицирует транзакцию, сгенерировавшую запись журнала.
- Begin Time – Время начала транзакции.
- Transaction Name – Имя транзакции (например, INSERT, DELETE).
- Transaction SID – Кто её начал (используйте функцию SUSER_SNAME для отображения понятного имени).
- Operation – Тип выполненной операции (например, LOP_INSERT_ROWS, LOP_DELETE_ROWS или LOP_COMMIT_XACT).
- Context – Тип затронутой страницы или структуры (например, LCX_CLUSTERED, LCX_HEAP или LCX_PFS).
- Page ID – Идентификатор физического файла и страницы (номер страницы в шестнадцатеричном формате).
- Slot ID – Запись на странице.
- Current LSN – Уникальный LSN, идентифицирующий запись журнала.
- Description – Дополнительные сведения, описывающие зарегистрированное изменение.
Запрос к журналу транзакций
Для этой демонстрации процесс очистки фантомов отключен перед запуском кода из Статьи 3. Поэтому мы увидим записи, отмеченные для удаления, и у нас будет время просмотреть журнал транзакций и то, как это связано со страницами.
/* Запустите код из Статьи 2 */
/* Отключите процесс очистки фантомов */
DBCC TRACEON(661, -1);
/* Запустите код из Статьи 3 */
/* Просмотр журнала транзакций */
USE GhostDemo;
DROP TABLE IF EXISTS #TLog;
SELECT
[Transaction ID]
, [Begin Time]
, [Transaction Name]
, SUSER_SNAME ([Transaction SID]) AS [Started By]
, [End Time]
, [Operation]
, Context
, CONVERT(int, CONVERT(varbinary(4), SUBSTRING([Page ID], 6, 8), 2)) AS [Decimal Page ID]
, [Slot ID]
, [Current LSN]
, [Description]
INTO
#TLog
FROM
fn_dblog (NULL, NULL) as tl
WHERE
SUSER_SNAME ([Transaction SID]) NOT IN ('sa', 'NT SERVICE\SQLTELEMETRY')
OR
[Transaction SID] IS NULL;
/* Показать все записи журнала в журнале транзакций, упорядоченные по Current LSN */
SELECT
*
FROM
#TLog
ORDER BY [Current LSN];
Демонстрация показывает, что удаленные записи помечены как фантомные:
Изображение: результаты запроса, показывающие LOP_BEGIN_XACT, LOP_DELETE_ROWS | LCX_MARK_AS_GHOST, LOP_SET_BITS | LCX_PFS, LOP_COMMIT_XACT
Это показывает:
- Начало транзакции (LOP_BEGIN_XACT).
- Первая запись помечается как фантомная (LOP_DELETE_ROWS | LCX_MARK_AS_GHOST).
- Страница PFS обновляется; это отображается в другом контексте транзакции, поскольку SQL Server обновляет метаданные выделения с использованием внутренних системных операций. (LOP_SET_BITS | LCX_PFS) – столбец details показывает изменение, указывая, что GhostBit будет записан для страницы.
- Транзакция фиксируется (LOP_COMMIT_XACT).
Каждая запись существует в слоте в массиве слотов на странице данных. Каждая запись, помеченная как фантомная, имеет Slot ID; это ссылка на физическое расположение страницы, а не постоянный идентификатор. Поскольку мы удалили 11 записей, отображается 11 уникальных Slot ID.
Примечание: Page ID, возвращаемый fn_dblog(), является шестнадцатеричным значением; код выше преобразует его в десятичное для более легкого сравнения с DBCC PAGE и метаданными выделения; в описании (Description) по-прежнему будет отображаться шестнадцатеричная версия.
Просмотр страницы
Используя DBCC PAGE (показано в прошлой статье), мы можем просмотреть страницу 352 и увидеть изменение:
/* Просмотр данных страницы */
DBCC PAGE(GhostDemo, 1, 352, 3);
Изображение: вывод DBCC PAGE, показывающий m_slotCnt = 12 и m_ghostRecCnt = 11
Заголовок показывает количество слотов (m_slotCnt = 12), которое теперь можно связать с Slot ID в журнале транзакций. На данный момент количество фантомных записей в заголовке страницы (m_ghostRecCnt = 11) соответствует количеству строк, помеченных как фантомные в журнале транзакций LCX_MARK_AS_GHOST, записанных для 11 слотов. Мы также видим используемую страницу PFS (1:1).
Как показано в прошлой статье, записи, которые стали фантомными, теперь помечены как таковые на странице данных рядом со своей строкой:
Изображение: фантомные записи на странице данных
Страница PFS
DBCC PAGE также можно использовать для просмотра данных на странице PFS:
DBCC PAGE(GhostDemo, 1, 1, 3);
Как видно в журнале транзакций, запись PFS для страницы 352 обновлена, и установлен GhostBit, указывающий, что страница содержит фантомные записи.
Изображение: вывод DBCC PAGE для страницы PFS с установленным GhostBit
На данный момент…
11 записей были удалены, и это можно увидеть как в журнале транзакций, так и на страницах данных и PFS.
Включение очистки фантомов
/* Когда все необходимые страницы просмотрены, включите процесс очистки фантомов обратно */
DBCC TRACEOFF(661, -1);
Повторный просмотр журнала транзакций покажет LOP_EXPUNGE_ROWS | LCX_CLUSTERED — это процесс очистки фантомов, физически удаляющий данные строк со страницы 352 (в данном примере это кластерный индекс). Также будет показано обновление PFS, где флаг GhostBit для страницы снимается.
Изображение: записи LOP_EXPUNGE_ROWS и обновление PFS в журнале транзакций
Полный запуск с включенной очисткой фантомов
Обычная операция удаления следует тому же процессу, но физическое удаление строк может произойти быстрее, в зависимости от активности системы и времени выполнения задачи очистки фантомов. В малонагруженной демонстрационной системе очистка, скорее всего, будет быстрой.
Демонстрация полного жизненного цикла
Следующая демонстрация обновляет базу данных, затем добавляет и удаляет записи, показывая полный жизненный цикл процесса.
/* Освежим базу данных */
USE master;
GO
ALTER DATABASE GhostDemo SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE GhostDemo;
GO
/* Создадим демонстрационную базу данных, таблицу и вставим данные */
CREATE DATABASE GhostDemo;
GO
USE GhostDemo;
GO
CREATE TABLE [dbo].[ViewGhost](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](100) NULL
) ON [PRIMARY];
GO
CREATE CLUSTERED INDEX PK_ViewGhost
ON dbo.ViewGhost
(ID);
GO
/* Создаем фантомы */
USE [GhostDemo];
/* Выводим результаты в окно запроса */
DBCC TRACEON(3604);
GO
/* Вставляем образцы строк */
INSERT INTO
dbo.ViewGhost
(Name)
VALUES
('RowText')
GO 12
/* Логически удаляем все строки, кроме одной */
DELETE
FROM
dbo.ViewGhost
WHERE
ID > 1;
GO
После удаления записей мы можем просмотреть журнал транзакций. В зависимости от активности системы задача очистки фантомов может выполниться быстро или быть отложена. Если удаление записей не видно сразу, подождите немного и попробуйте снова; в конечном итоге они будут физически удалены.
/* Получение данных журнала транзакций */
DROP TABLE IF EXISTS #TLog;
SELECT
[Transaction ID]
, [Begin Time]
, [Transaction Name]
, SUSER_SNAME ([Transaction SID]) AS [Started By]
, [End Time]
, [Operation]
, Context
, CONVERT(int, CONVERT(varbinary(4), SUBSTRING([Page ID], 6, 8), 2)) AS [Decimal Page ID]
, [Slot ID]
, [Current LSN]
, [Description]
INTO
#TLog
FROM
fn_dblog (NULL, NULL) as tl
WHERE
(
SUSER_SNAME ([Transaction SID]) NOT IN ('sa', 'NT SERVICE\SQLTELEMETRY')
OR
[Transaction SID] IS NULL
)
AND
(
(
[Transaction Name] NOT LIKE '%QDS%'
AND
[Transaction Name] <> 'UpdateQPStats'
)
OR
[Transaction Name] IS NULL
);
/* Показать все записи журнала в журнале транзакций, упорядоченные по Current LSN */
SELECT
*
FROM
#TLog
ORDER BY [Current LSN];
Сначала мы видим появление фантомов…
Изображение: записи LOP_DELETE_ROWS, LOP_SET_BITS, LOP_COMMIT_XACT
…затем физическое удаление, которое не является частью пользовательской транзакции DELETE; оно выполняется асинхронно задачей очистки фантомов.
Изображение: записи LOP_EXPUNGE_ROWS и LOP_SET_BITS для снятия GhostBit
Резюме
В этой статье был показан полный жизненный цикл операции удаления, использующей механизм фантомных записей SQL Server, и то, как каждый этап представлен внутри системы.
Используя недокументированную функцию fn_dblog(), мы смогли увидеть, что операция удаления не является немедленным физическим удалением строк. Вместо этого SQL Server сначала выполняет логическое удаление, помечая строки как фантомные:
- LOP_BEGIN_XACT — регистрирует начало транзакции удаления.
- LOP_DELETE_ROWS | LCX_MARK_AS_GHOST — регистрирует каждую строку, логически удаляемую путем пометки её как фантомной записи.
- LOP_SET_BITS | LCX_PFS — обновляет метаданные PFS, устанавливая GhostBit для затронутой страницы, чтобы указать на наличие фантомных записей.
- LOP_COMMIT_XACT — завершает пользовательскую транзакцию, оставляя фантомные записи физически присутствующими на странице данных.
Используя DBCC PAGE, мы затем смогли сопоставить записи журнала транзакций с физической страницей данных. Slot ID из журнала транзакций идентифицировал отдельные затронутые записи, в то время как заголовок страницы показывал увеличение m_ghostRecCnt и подтверждал, что строки оставались на странице в фантомном состоянии.
Страница PFS предоставила еще один взгляд на то же изменение: GhostBit был установлен для записи страницы данных, позволяя SQL Server идентифицировать страницы, содержащие фантомные записи, требующие очистки.
Как только процесс очистки фантомов был снова включен, асинхронная задача очистки физически удалила строки со страницы. Это было видно в журнале транзакций через:
- LOP_EXPUNGE_ROWS | LCX_CLUSTERED — показывающее физическое удаление фантомных записей.
- LOP_SET_BITS | LCX_PFS — снимающее GhostBit, как только страница больше не содержала фантомных записей.
Полный процесс демонстрирует разделение между логическим удалением и физическим удалением.







