(Эта статья впервые появилась на SQLperformance.com четыре года назад как часть серии публикаций, до того как этот сайт был законсервирован в конце 2022 года и серия была прервана. Переопубликована здесь с разрешения, с небольшими правками.)
За всю мою карьеру в сфере данных, как внутри Microsoft, так и в качестве консультанта, я обнаружил, что одна из самых непонятых частей SQL Server — это журнал транзакций. Недостаток знаний о том, как работает журнал транзакций и как им нужно управлять, или просто распространённые заблуждения, могут привести к различным проблемам в производственной среде, включая:
- Неуправляемый рост журнала транзакций и потенциальное исчерпание свободного места.
- Проблемы с производительностью из-за повторяющихся операций сжатия журнала транзакций.
- Проблемы с производительностью из-за явления, известного как фрагментация виртуальных файлов журнала (VLF), о котором я писал в этой статье.
- Невозможность восстановить базу данных до желаемой точки во времени с использованием резервных копий журнала транзакций.
- Невозможность выполнить резервное копирование заключительного фрагмента журнала (tail-log backup) во время аварийного восстановления (см. здесь объяснение таких копий).
- Различные проблемы, связанные с переключением при отказе (failover) и производительностью восстановления.
С этой статьи я начинаю серию публикаций (теперь уже здесь, в моём блоге SQLskills) о журнале транзакций: как он работает и как им следует управлять, и в её рамках я затрону все перечисленные выше проблемы. В этой статье я объясню, что такое журналирование и почему оно необходимо.
Базовая терминология, связанная с журналированием
Когда я рассказываю о каком-либо механизме внутри SQL Server, я сталкиваюсь с проблемой «курицы и яйца»: мне нужно использовать слово или фразу до того, как я их объяснил. Чтобы избежать этой проблемы в рамках данной серии, я начну с объяснения некоторой терминологии, которая потребуется при обсуждении журналирования, и я буду раскрывать многие из этих терминов по мере развития серии.
Транзакция, фиксация (Commit) и откат (Rollback)
Транзакция охватывает одно изменение или набор изменений в базе данных. Она имеет определённое начало и определённый конец. Начало — это когда используется оператор BEGIN TRANSACTION или SQL Server автоматически начинает транзакцию за вас. Конец может быть одним из четырёх событий:
- Транзакция фиксируется при выполнении оператора
COMMIT TRANSACTION. - Транзакция фиксируется автоматически SQL Server в случае транзакции с автоподтверждением (autocommit).
- Транзакция завершает откат после выполнения оператора
ROLLBACK TRANSACTION. - Транзакция завершает откат после возникновения проблемы, когда SQL Server автоматически откатил транзакцию.
Когда транзакция фиксируется, изменения, которые она внесла, окончательно закрепляются в базе данных и становятся устойчивыми (durable) в журнале транзакций на диске. Обратите внимание, я сказал «в журнале транзакций». Фактические изменения страниц файлов данных в памяти *не* записываются на диск в момент фиксации транзакции. Им не нужно быть устойчивыми в файлах данных, потому что изменения уже устойчивы в журнале транзакций. В конечном итоге страницы файлов данных будут записаны на диске операцией контрольной точки (checkpoint) (или, возможно, из-за работы фонового модуля записи «ленивого писателя» (lazywriter)).
И наоборот, когда транзакция откатывается, внесённые ею изменения данных больше не присутствуют в базе данных. В базе данных всё ещё будут присутствовать некоторые физические изменения, поскольку откат транзакции означает выполнение дополнительных изменений, но вы можете думать об откаченной транзакции как о не повлиявшей на данные в базе.
Операции контрольных точек и отката заслуживают отдельных статей, поэтому я объясню их позже в этой серии.
Я обсуждаю эти три термина более подробно в руководстве Introduction to SQL Server Transactions в блоге SentryOne.
Журналирование, записи журнала и журнал транзакций
Журналирование — это просто создание устойчивого описания изменения в базе данных, почти всегда в контексте транзакции. Когда изменение выполняется, оно описывается в записи журнала. Запись журнала обычно содержит достаточно информации, чтобы при необходимости можно было воспроизвести это изменение в базе данных или откатить его.
Эта запись журнала изначально находится в памяти и может быть записана на диск до фиксации транзакции, но она обязательно должна быть записана на диск до того, как транзакция сможет завершить фиксацию, иначе транзакция не будет устойчивой. Исключением из этого правила является ситуация, когда включена функция отложенной устойчивости (delayed durability), о которой Аарон Бертран рассказывает в этой статье.
Записи журнала хранятся в журнале транзакций (одном или нескольких файлах журнала на диске), который имеет довольно сложную внутреннюю архитектуру. Я расскажу об этом, а также о многом другом, касающемся записей журнала, в следующей статье серии.
Аварийное восстановление (Crash Recovery)
Аварийное завершение (crash) — это ситуация, когда SQL Server неожиданно завершает работу, и различные изменённые базы данных не были корректно остановлены (с обеспечением записи всех изменённых страниц файлов данных на диск и пометкой базы данных как корректно завершённой).
При запуске SQL Server проверяет все базы данных, чтобы определить, не была ли какая-либо из них помечена как корректно завершённая. Если такая база найдена, она должна пройти через аварийное восстановление. Это обеспечивает следующее:
- Для любой транзакции, которая была зафиксирована до аварии: убедиться, что все изменения этой транзакции отражены в базе данных (т.е. воспроизвести транзакцию).
- Для любой транзакции, которая не была зафиксирована до аварии: убедиться, что ни одно из изменений этой транзакции не отражено в базе данных (т.е. откатить транзакцию).
Другими словами, аварийное восстановление приводит базу данных в транзакционную согласованность на момент возникновения аварии. Аварийное восстановление используется:
- При запуске SQL Server, если обнаружена база данных, которую нужно восстановить.
- Во время переключения на вторичную копию базы данных.
- В конце последовательности восстановления с использованием резервных копий (см. здесь).
Аварийное восстановление — это сложный процесс, и для его детального объяснения потребуется ещё несколько статей в этой серии.
Зачем нужно журналирование?
Самая базовая причина для журналирования — позволить SQL Server сделать транзакции устойчивыми, чтобы их можно было восстановить в процессе аварийного восстановления или откатить при необходимости в ходе обычных операций с базой данных. Если бы журналирования не было, база данных после аварии была бы транзакционно несогласованной и, возможно, даже структурно повреждённой.
Однако без журналирования также была бы невозможна целая группа других функций SQL Server, включая:
- Резервное копирование данных, которое можно согласованно восстановить.
- Резервное копирование журнала транзакций, которое можно использовать во время операции восстановления и для реализации доставки журналов (log shipping).
- Репликация, которая опирается на возможность извлечения транзакций из журнала транзакций.
- Отслеживание изменённых данных (Change Data Capture, CDC), которое использует агент чтения журнала (Log Reader Agent) репликации транзакций для сбора изменений из журнала транзакций.
- Потоковая передача событий изменений (Change Event Streaming), новая функция SQL Server 2025, похожая на CDC, но взаимоисключающая друг друга и использующая другой механизм сканирования журнала.
- Зеркальное отображение базы данных и группы доступности (availability groups), которые зависят от отправки записей журнала на вторичные копии базы данных для их воспроизведения.
SQL Server (и все аналогичные серверы баз данных) использует так называемое упреждающее журналирование (write-ahead logging). Это означает, что описания изменений должны быть записаны на диск раньше, чем сами изменения, чтобы гарантировать возможность корректного аварийного восстановления базы данных. Если бы изменение было записано в файл данных до записей журнала, описывающих его, и SQL Server завершился аварийно, не было бы способа понять, что нужно откатывать, и база данных оказалась бы несогласованной. Этот порядок является инвариантом, независимо от уровня изоляции, типа транзакции или использования функции отложенной устойчивости. Сначала записи журнала, потом страницы данных.
Лишь верхушка айсберга
Как вы можете видеть из этой вводной статьи, в журнал транзакций и механизм журналирования в SQL Server вложено огромное количество деталей, и всё, что я сделал до сих пор, — это определил некоторую высокоуровневую терминологию и объяснил, зачем нужно журналирование. Надеюсь, вы присоединитесь ко мне, когда я буду углубляться и расширять тему по мере развития серии!
-- Пример операторов управления транзакциями
BEGIN TRANSACTION;
-- Выполнение некоторых изменений
UPDATE dbo.MyTable SET Column1 = 'NewValue' WHERE ID = 1;
-- Фиксация изменений
COMMIT TRANSACTION;
-- Или откат
BEGIN TRANSACTION;
DELETE FROM dbo.MyTable WHERE ID = 2;
-- Отмена изменений
ROLLBACK TRANSACTION;

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