17.6.26

Журнал транзакций SQL Server. Часть 4: записи журнала

Автор: 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 или 0
  • LOP_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 124
  • 00000030:000000af:0002 LOP_SET_BITS LCX_DIFF_MAP 0000:00000000 56
  • 00000030:000000af:0003 LOP_MODIFY_ROW LCX_HEAP 0000:000004be 104
  • 00000030: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, и точку восстановления для её возврата.

Поскольку я теперь заложил основу для понимания записей журнала, в следующей статье этой серии я покажу более глубокие примеры, включая то, как работает откат транзакций. И не стесняйтесь оставлять комментарии с любыми вопросами, которые у вас могут возникнуть.



Комментариев нет:

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