20.2.26

Журнал транзакций SQL Server, Часть 2: Архитектура журналирования

Автор: Paul Randal, The SQL Server Transaction Log, Part 2: Log Architecture

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

Примечание: по мере того как я буду продвигаться по этой серии и рассказывать об аспектах журнала, часто встречаются небольшие пограничные случаи или странное поведение в специфических обстоятельствах, которые были добавлены или изменены с годами. Я буду их игнорировать, если только специально не захочу на них указать, иначе посты были бы переполнены кроличьими норами и лабиринтами извилистых запутанных проходов (да, я любил текстовые приключенческие игры 80-х :-), которые отвлекали бы от главных вещей, которые нужно усвоить в каждой части.

Структурная иерархия

Журнал транзакций внутренне организован с использованием трёхуровневой иерархии, как показано на рисунке 1 ниже.

Рисунок 1: Трёхуровневая структурная иерархия журнала транзакций (моё изображение)

Журнал транзакций содержит несколько виртуальных файлов журнала, которые содержат блоки журнала, хранящие фактические записи журнала.

Виртуальные файлы журнала

Журнал транзакций разделён на разделы, называемые виртуальными файлами журнала, обычно называемые просто VLF. Это сделано для упрощения управления операциями в журнале транзакций для менеджера журнала в SQL Server. Вы не можете указать, сколько VLF создаётся SQL Server при первом создании базы данных или автоматическом росте файла журнала, но вы можете на это повлиять. Алгоритм того, сколько VLF создаётся, выглядит следующим образом:

  • Размер файла журнала менее 64 МБ: создать 4 VLF, каждый размером примерно 16 МБ
  • Размер файла журнала от 64 МБ до 1 ГБ: создать 8 VLF, каждый размером примерно 1/8 от общего размера
  • Размер файла журнала более 1 ГБ: создать 16 VLF, каждый размером примерно 1/16 от общего размера

До SQL Server 2014, когда файл журнала автоматически увеличивается, количество новых VLF, добавляемых в конец файла журнала, определялось приведённым выше алгоритмом на основе размера авто-прироста. Однако, используя этот алгоритм, если размер авто-прироста невелик, а файл журнала подвергается множеству авто-увеличений, это может привести к очень большому количеству маленьких VLF (называемых фрагментацией VLF), что может стать серьёзной проблемой производительности для некоторых операций (см. здесь).

Из-за этой проблемы в SQL Server 2014 алгоритм авто-прироста файла журнала изменился. Если размер авто-прироста меньше 1/8 от общего размера файла журнала, создаётся только 1 новый VLF, в противном случае используется старый алгоритм. Это значительно сокращает количество VLF для файла журнала, который подвергся большому объёму авто-прироста, и я объяснил пример различия в этой статье блога.

Каждый VLF имеет порядковый номер, который уникально его идентифицирует и используется в различных местах, что я объясню ниже и в будущих частях. Можно было бы подумать, что порядковые номера начинаются с 1 для совершенно новой базы данных, но это не так.

На экземпляре SQL Server 2019 я создал новую базу данных, не указывая размеров файлов, а затем проверил VLF с помощью кода ниже:

CREATE DATABASE NewDB;
GO

SELECT
    [file_id],
    [vlf_begin_offset],
    [vlf_size_mb],
    [vlf_sequence_number]
FROM
    sys.dm_db_log_info (DB_ID (N'NewDB'));
GO

Обратите внимание, что динамическое административное представление sys.dm_db_log_info было добавлено в SQL Server 2016 SP2. До этого (а также сегодня, поскольку оно всё ещё существует) вы могли использовать недокументированную команду DBCC LOGINFO, но вы не можете задать ей список выбора – просто выполните DBCC LOGINFO (N'NewDB'), и порядковые номера VLF находятся в столбце FSeqNo результирующего набора.

В любом случае, результаты запроса к sys.dm_db_log_info были следующими:

file_id vlf_begin_offset vlf_size_mb vlf_sequence_number
------- ---------------- ----------- -------------------
2       8192             1.93        37
2       2039808          1.93        0
2       4071424          1.93        0
2       6103040          2.17        0

Обратите внимание, что первый VLF начинается со смещения 8 192 байта в файле журнала. Это связано с тем, что все файлы базы данных, включая журнал транзакций, имеют страницу заголовка файла, которая занимает первые 8 КБ и хранит различные метаданные о файле.

Итак, почему SQL Server выбирает 37, а не 1 для порядкового номера первого VLF? Ответ заключается в том, что он находит наибольший порядковый номер VLF в базе данных model, а затем для любой новой базы данных первый VLF журнала транзакций использует этот номер плюс 1 в качестве своего порядкового номера. Я не знаю, почему этот алгоритм был выбран в глубине времён, но так было по крайней мере с SQL Server 7.0.

Чтобы доказать это, я выполнил этот код:

SELECT
    MAX ([vlf_sequence_number]) AS [Max_VLF_SeqNo]
FROM
    sys.dm_db_log_info (DB_ID (N'model'));
GO

И результаты были следующими:

Max_VLF_SeqNo
-------------
36

Так что вот оно.

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

Блоки журнала

Каждый VLF содержит небольшой заголовок метаданных, а остальное пространство заполняется блоками журнала. Каждый блок журнала изначально имеет размер 512 байт и будет увеличиваться с шагом 512 байт до максимального размера 60 КБ, после чего он должен быть записан на диск. Блок журнала может быть записан на диск до достижения максимального размера, если происходит одно из следующих событий:

  • Транзакция фиксируется, и для этой транзакции не используется отложенная долговечность (delayed durability), поэтому блок журнала должен быть записан на диск, чтобы транзакция была долговечной
  • Используется отложенная долговечность, и срабатывает фоновый таймер ‘flush the current log block to disk’ (сбросить текущий блок журнала на диск) с интервалом 1 мс
  • Страница файла данных записывается на диск контрольной точкой или отложенной записью (lazy writer), и в текущем блоке журнала есть одна или несколько записей журнала, которые влияют на страницу, которая вот-вот будет записана (помните, что упреждающее журналирование (write-ahead logging) должно быть гарантировано).

Вы можете рассматривать блок журнала как нечто вроде страницы переменного размера, которая хранит записи журнала в том порядке, в котором они создаются транзакциями, изменяющими базу данных. Для каждой транзакции нет отдельного блока журнала; записи журнала для нескольких параллельных транзакций могут перемежаться в одном блоке журнала. Можно подумать, что это создаст трудности для операций, которым нужно найти все записи журнала для одной транзакции, но это не так, как я объясню, когда буду рассказывать о том, как работают откаты транзакций в одной из следующих частей.

Кроме того, когда блок журнала записывается на диск, вполне возможно, что он содержит записи журнала от незафиксированных транзакций. Это также не является проблемой из-за того, как работает восстановление после сбоя – это тема для нескольких следующих частей в серии.

Номера последовательности журнала

Блоки журнала имеют идентификатор внутри VLF, начиная с 1 и увеличиваясь на 1 для каждого нового блока журнала в VLF. Записи журнала также имеют идентификатор внутри блока журнала, начиная с 1 и увеличиваясь на 1 для каждой новой записи журнала в блоке журнала. Таким образом, все три элемента в структурной иерархии журнала транзакций имеют идентификатор, и они объединяются в тройной идентификатор, называемый номером последовательности журнала, чаще называемый просто LSN.

LSN определяется как <порядковый номер VLF>:<идентификатор блока журнала>:<идентификатор записи журнала> (4 байта: 4 байта: 2 байта) и уникально идентифицирует одну запись журнала. Это постоянно увеличивающийся идентификатор, потому что порядковые номера VLF увеличиваются бесконечно.

Основа заложена!

Хотя о VLF полезно знать, по моему мнению, LSN — это самая важная концепция для понимания в реализации журналирования SQL Server, поскольку LSN являются краеугольным камнем, на котором построены откат транзакций и восстановление после сбоев, и LSN будут снова и снова всплывать по мере моего продвижения по серии. В следующем части я расскажу об усечении журнала и циклической природе журнала транзакций, что целиком связано с VLF и тем, как они повторно используются.



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

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