Что обеспечивает правило, по которому запись о фиксации должна попасть в журнал раньше, чем изменённые страницы данных?
Это правило называется Write-Ahead Logging, WAL — опережающая запись в журнал. Оно гарантирует, что после сбоя СУБД сможет восстановить зафиксированные изменения или удалить незавершённые, даже если страницы таблиц сохранялись не полностью.
Важно: изменённая страница данных может попасть на диск до фиксации транзакции. Запрещено лишь записывать её раньше соответствующей записи журнала. Запись о COMMIT должна быть надёжно сохранена до того, как СУБД сообщит клиенту об успешной фиксации.
Базы данных работают с буферным кэшем: изменения сначала выполняются в памяти, а запись страниц таблиц на диск откладывается ради производительности. При аварийном отключении часть страниц может оказаться сохранённой, а часть — нет.
Если бы восстановление зависело от полного сохранения всех страниц, СУБД пришлось бы синхронно записывать множество данных при каждом подтверждении транзакции. WAL решает эту проблему: вместо немедленной записи всех страниц достаточно последовательно сохранить небольшой журнал операций, по которому изменения можно повторить или отменить.
Представим транзакцию, которая изменила несколько страниц. Сбой может произойти в любой момент: после записи только части страниц, после записи всех страниц, но до сохранения признака фиксации, либо сразу после фиксации.
Без корректного порядка записи возможны опасные состояния:
Поэтому нужно отдельно обеспечить атомарность восстановления и durability — сохранность подтверждённых изменений.
Для изменяемой страницы журнал содержит записи, описывающие операцию или позволяющие восстановить её результат. У страницы есть номер последней применённой записи журнала — LSN. Перед записью страницы на диск журнал с LSN не меньше этого значения должен быть сброшен на диск.
При фиксации транзакции журнал получает специальную запись COMMIT. СУБД подтверждает клиенту успешную фиксацию только после того, как эта запись стала устойчивой. Измененные страницы данных при этом могут оставаться в памяти.
После сбоя восстановление обычно состоит из трёх логических этапов:
Главный компромисс WAL — последовательная запись журнала ускоряет фиксацию по сравнению с принудительной записью всех страниц, но требует дополнительного дискового пространства, операций восстановления и корректного управления журналом. Удалять часть журнала можно только тогда, когда она больше не нужна для восстановления и репликации.
WAL не заменяет резервное копирование и не гарантирует сохранность данных при физическом разрушении диска. Он защищает согласованность и подтверждённые изменения при сбое процесса, сервера или питания в пределах возможностей конкретной системы хранения.
Транзакция списывает деньги со счёта и получает успешный ответ COMMIT. Сразу после этого сервер отключается, а изменённые страницы ещё находятся в буферном кэше.
Вариант с принудительной записью всех страниц до ответа клиенту проще для рассуждения, но он медленный: одна транзакция может затронуть много страниц, а случайные записи плохо масштабируются при высокой нагрузке.
Вариант с WAL сначала надёжно сохраняет журнал и запись фиксации, а страницы данных сбрасывает позже. После перезапуска СУБД читает журнал и повторяет списание, если сама страница не успела сохраниться. Этот вариант выбран, потому что обеспечивает сохранность результата и допускает эффективную последовательную запись журнала.
Результат: клиентский COMMIT не теряется из-за того, что страницы данных не были сброшены до сбоя. Цена решения — задержка журналирования, место на диске и время восстановления.
Нет. Это как раз не требуется и обычно было бы слишком дорого. Достаточно, чтобы журнал, описывающий изменения, и устойчивая запись COMMIT были сохранены раньше подтверждения транзакции. При восстановлении отсутствующие страницы будут обновлены повторным применением журнала.
Это допустимо, если соответствующая часть журнала уже записана по правилу WAL. При восстановлении СУБД видит, что транзакция не имеет устойчивого COMMIT, и отменяет её изменения либо использует механизм версий и служебных записей, чтобы они не стали видимыми как результат завершённой транзакции.
Следовательно, WAL допускает раннюю запись грязных страниц, но не допускает потерю журнала, необходимого для их восстановления.
Потому что содержимое памяти может исчезнуть при сбое. Если клиент получил подтверждение, а запись COMMIT не попала на устойчивый носитель, после восстановления СУБД может классифицировать транзакцию как незавершённую и отменить её изменения.
СУБД может использовать group commit и совместно сбрасывать журнал для нескольких транзакций, снижая стоимость синхронной записи. Но для каждой подтверждённой транзакции всё равно должна существовать устойчивая информация, позволяющая восстановлению однозначно признать её зафиксированной.