Автор: Paul Randal, The SQL Server Transaction Log, Part 4: Log Records
Ранее я рассказывал о том, зачем нужно ведение журнала, об архитектуре и циклической природе журнала, и теперь пришло время взглянуть на самое сердце системы ведения журнала — на сами записи журнала. Как всегда, рекомендую прочитать предыдущие статьи в этой серии, прежде чем читать эту.
Что такое записи журнала?
Самое простое определение записи журнала: она описывает одно изменение в базе данных. Одна операция в базе данных может вызвать несколько изменений, но каждое изменение обычно имеет свою собственную запись журнала для его описания. Примером может служить обновление столбца в одной строке — оно приведёт к следующему:
- Выполнение обновления, которое генерирует одну запись журнала.
- Если это первый раз, когда экстент страницы (содержащей обновлённую строку) входит в часть того, что было изменено с момента последней полной резервной копии, должен быть установлен бит в соответствующей дифференциальной битовой карте, что генерирует ещё одну запись журнала.
Каждая запись журнала имеет фиксированные накладные расходы, поэтому иногда компонент Storage Engine нарушает это правило, объединяя несколько изменений в одну запись журнала, чтобы уменьшить количество записей, записываемых в журнал. Некоторые примеры этого:
- Когда происходит обновление нескольких столбцов в одной строке таблицы, одна запись журнала может содержать все изменения столбцов для одной строки вместо одной записи на изменение столбца. Обратите внимание, что если какие-либо некластерные индексы также необходимо обновить, эти изменения будут в отдельных записях журнала.
- Когда индекс строится или перестраивается, Storage Engine заполнит целую страницу строками, а затем использует одну запись журнала для записи всего образа страницы вместо одной записи на строку, вставленную в новую страницу.
Каждая запись журнала имеет номер последовательности журнала (Log Sequence Number, LSN), который я определил во второй части как <номер VLF>:<идентификатор блока журнала>:<идентификатор записи журнала> (4 байта: 4 байта: 2 байта). Каждый LSN уникален, постоянно возрастает и идентифицирует физическое расположение записи журнала в журнале. Записи журнала никогда не перемещаются в журнале, поэтому их LSN никогда не меняются после создания. Заголовок страницы для каждой страницы файла данных в базе данных содержит LSN самой последней записи журнала, описывающей изменение на этой странице, и используется во время аварийного восстановления, которое я освещу в будущей статье.
Содержимое записей журнала
Вы можете думать о записи журнала как о записи таблицы — она имеет определённую структуру, состоящую из нескольких полей. Все записи журнала имеют общий заголовок с метаданными о записи журнала, а затем могут иметь различные дополнительные поля в зависимости от типа записи журнала. Некоторые поля заголовка включают:
LSNзаписи журнала- Тип записи журнала
- Контекст изменения, если есть (например, куча или кластерный индекс)
- Идентификатор транзакции, частью которой является изменение, если есть. Некоторые записи журнала нетранзакционны и их нельзя откатить — например, изменения в страницах
PFSи дифференциальных битовых картах. - Длина записи журнала в байтах
LSNпредыдущей записи журнала, сгенерированной транзакцией, если есть- Объём свободного места, зарезервированного в журнале, на случай, если запись журнала потребуется откатить
Менеджер журнала всегда должен обеспечивать наличие достаточного свободного места в журнале для генерации любых записей журнала, необходимых для отката транзакции, без необходимости увеличения размера журнала, что делает резервирование места в журнале критически важным. Если бы это было не так, и откат транзакции потребовал бы увеличения журнала, а затем это не удалось бы (например, из-за нехватки места на диске журнала), база данных должна была бы быть переведена в автономный режим в состоянии SUSPECT. Вы можете прочитать немного больше об откатах в моей статье Введение в транзакции SQL Server, и я подробно освещу их в следующей статье.
Записи журнала, описывающие изменения страниц таблиц/индексов, также включают:
- Идентификатор единицы распределения, которой принадлежит страница
- Идентификатор страницы и идентификатор слота изменяемой строки
- Значения ключей изменяемой строки, если есть
- Список блокировок, которые удерживались для защиты изменения
- Смещение изменения в строке
- Образы «до» и «после» изменения или массив образов «до» и «после», если изменяются несколько частей строки
Образы «до» и «после» — это набор изменённых байтов и то, на что они были изменены соответственно, и они позволяют откатить или воспроизвести изменение. Некоторые записи журнала содержат только образ «после», поскольку они не могут быть откатаны, что делает образ «до» ненужным — например, запись журнала, сгенерированная как часть отката другой записи журнала или нетранзакционная запись журнала.
В зависимости от типа записи журнала может присутствовать гораздо больше полей, и мы встретим некоторые из них по мере продвижения в этой серии.
Типы записей журнала
Существует от 70 до 100 типов записей журнала в зависимости от версии SQL Server (нет документированного списка). Все имена записей журнала начинаются с LOP, что означает Log Operation, и некоторые общие типы включают:
LOP_FORMAT_PAGE– создание нового образа страницыLOP_MODIFY_ROW– изменение строки от 1 до 16 смежных байтов, потенциально затрагивающее несколько соседних столбцовLOP_MODIFY_COLUMNS– изменение нескольких разрозненных столбцов в строкеLOP_SET_BITS– установка некоторых битов в битовой карте распределения в 1 или 0LOP_INSERT_ROWS– вставка одной или нескольких строк на страницуLOP_DELETE_ROWS– удаление одной или нескольких строк на страницуLOP_EXPUNGE_ROWS– используется задачей очистки фантомных записей при удалении строк со страницыLOP_BEGIN_XACT– всегда первая запись журнала, генерируемая транзакциейLOP_COMMIT_XACT– запись журнала, обозначающая, что транзакция успешно зафиксированаLOP_ABORT_XACT– запись журнала, обозначающая, что транзакция завершила откат
Изучение записей журнала
Способ изучения записей журнала — через недокументированную функцию fn_dblog. Она требует два параметра LSN, указывающих начальную и конечную точки в журнале, но я редко их использую и вместо этого указываю NULL для обоих, что означает сканирование всех доступных записей журнала в активных VLF. Замечательная особенность этой функции в том, что она позволяет ограничить, какие поля выводить (в противном случае вы получите все 130), и вы можете указывать условия.
Примечание:
- Если вы хотите изучить все возможные записи журнала, включая те, что находятся в
VLF, помеченных как неактивные из-за усечения журнала, вы можете включить флаг трассировки 2537. - Если вы экспериментируете и хотите увидеть, какие записи журнала генерирует операция, используйте тестовую базу данных в режиме простого восстановления и выполните контрольную точку, чтобы усечь журнал. Это означает, что вы можете выполнить свою операцию, а затем
fn_dblogпокажет вам только записи журнала с момента контрольной точки.
Давайте рассмотрим простой пример. Я создал базу данных, таблицу с одним целочисленным столбцом, вставил одну строку и выполнил резервное копирование и контрольную точку. Теперь я сделаю следующее:
UPDATE TestTable SET Column1 = 2;
GO
SELECT
[Current LSN],
[Operation],
[Context],
[Transaction ID],
[Log Record Length]
FROM fn_dblog (NULL, NULL)
WHERE [Operation] NOT LIKE '%CKPT';
GO
Результат:
00000030:000000af:0001 LOP_BEGIN_XACT LCX_NULL 0000:000004be 12400000030:000000af:0002 LOP_SET_BITS LCX_DIFF_MAP 0000:00000000 5600000030:000000af:0003 LOP_MODIFY_ROW LCX_HEAP 0000:000004be 10400000030:000000af:0004 LOP_COMMIT_XACT LCX_NULL 0000:000004be 84
LSN указан в шестнадцатеричном формате, и вы можете видеть, что все четыре записи журнала для транзакции находятся в одном блоке журнала (000000AF). Вот что мы видим:
- Транзакция начинается с
LOP_BEGIN_XACTс идентификатором транзакции000004beи без контекста. - Поскольку обновление изменит экстент после резервного копирования, бит в дифференциальной битовой карте для него должен быть установлен с помощью
LOP_SET_BITSв контекстеLCX_DIFF_MAP. Обратите внимание, что запись журнала не имеет идентификатора транзакции, поскольку изменения дифференциальной карты нетранзакционны (т.е. они никогда не откатываются). - Собственно изменение регистрируется с помощью
LOP_MODIFY_ROWв контекстеLCX_HEAP(потому что таблица является кучей) и является частью транзакции. - Транзакция завершается с помощью
LOP_COMMIT_XACT.
Может быть увлекательно копаться в журнале, чтобы увидеть, что SQL Server делает для некоторых операций. Возможно, вы думаете, что это не имеет никакого применения в производственной среде, но вот статья, показывающая использование этой функции для выяснения того, кто выполнил DROP TABLE, и точку восстановления для её возврата.
Поскольку я теперь заложил основу для понимания записей журнала, в следующей статье этой серии я покажу более глубокие примеры, включая то, как работает откат транзакций. И не стесняйтесь оставлять комментарии с любыми вопросами, которые у вас могут возникнуть.

Комментариев нет:
Отправить комментарий