Стек: Java 11, PostgreSQL 12+, общие принципы СУБД
Цель: понять, как достигается свойство Atomicity, что такое WAL (Write-Ahead Logging), и как СУБД восстанавливается после аварийного отключения.
то такое Atomicity (атомарность) в ACID?
Atomicity — первое свойство в акрониме ACID — означает:
«Транзакция — это единая неделимая операция:
либо все её изменения применяются,
либо ни одно из них не применяется».
Пример: перевод денег
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- Снятие
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- Зачисление
COMMIT;
Если после первой операции отключится электричество — вторая не выполнится, и первая должна быть отменена.
Иначе деньги «испарятся».
💡 Вопрос: как СУБД помнит, что нужно откатить, если сервер упал?
Ответ — журнал транзакций (Write-Ahead Log, WAL).
Как достигается Atomicity: общий принцип (применим почти ко всем СУБД)
Все современные СУБД (PostgreSQL, Oracle, MySQL/InnoDB, SQL Server) используют один и тот же фундаментальный механизм:
Write-Ahead Logging (WAL) — «запись в журнал до записи на диск».
🔑 Основное правило WAL:
Любое изменение данных сначала записывается в журнал (log),
и только после этого — применяется к основным файлам данных.
Это гарантирует, что даже при сбое — вся информация для восстановления есть в журнале.
📦 Как это работает в PostgreSQL: под капотом
1. Структура данных
- Таблицы и индексы хранятся в файлах на диске (heap files).
- WAL-журнал — это последовательный файл (или набор файлов), в который записываются изменения в порядке их поступления.
2. Жизненный цикл транзакции
Шаг 1: Начало транзакции
- PostgreSQL присваивает транзакции уникальный XID (Transaction ID).
- Все изменения помечаются этим XID.
Шаг 2: Выполнение операций (UPDATE, INSERT, DELETE)
- Для каждой операции сразу же создаётся WAL-запись, содержащая:Тип операции,
XID транзакции,
Старое и новое значение (или данные для отката/повтора),
Позицию в файле данных. - Эта запись немедленно сбрасывается на диск (или в fsync, в зависимости от настроек).
⚠️ Ключевой момент:
Основные файлы данных НЕ изменяются немедленно!
Изменения сначала попадают в буферный кэш (shared_buffers),
а WAL-запись — на диск.
Шаг 3: COMMIT
- При COMMIT PostgreSQL:Записывает в WAL специальную запись «commit» с XID,
Сбрасывает WAL на диск (гарантируя, что запись устойчива к сбою),
Возвращает управление клиенту: «Транзакция завершена».
💡 Только после успешного fsync WAL-записи COMMIT считается завершённым!
Это — гарантия durability (D в ACID), но она же обеспечивает atomicity.
⚡ Что происходит при отключении питания?
Представим ваш сценарий:
- Начата транзакция перевода.
- Выполнено: UPDATE accounts SET balance = balance - 100 WHERE id = 1;
→ Создана WAL-запись для этого изменения. - Отключается электричество — до UPDATE ... +100 и COMMIT.
При перезапуске PostgreSQL выполняет recovery (восстановление):
- Читает WAL-журнал с последней контрольной точки (checkpoint).
- Находит незавершённые транзакции (те, у которых нет записи commit).
- Для каждой такой транзакции — выполняет откат (undo):Использует WAL-записи, чтобы восстановить предыдущее состояние строк,
Фактически «перематывает» изменения назад.
✅ Результат:
Счёт не уменьшился на 100,
потому что транзакция никогда не была завершена.
🔁 Это называется rollback on recovery.
🔄 А если транзакция была COMMIT-нута, но данные не записаны?
Сценарий:
- Обе операции выполнены.
- COMMIT отправлен — WAL-запись commit сброшена на диск.
- Электричество отключилось до того, как изменения попали в файлы данных.
При восстановлении:
- PostgreSQL видит в WAL: «транзакция XID завершена».
- Но в файлах данных — старые значения.
- Тогда выполняется redo (повтор):Все WAL-записи для этой транзакции применяются к данным,
Состояние базы доводится до консистентного.
✅ Результат:
Перевод успешно завершён,
даже если данные физически не были записаны до сбоя.
📊 WAL: не только для Atomicity
Хотя WAL основа для Atomicity и Durability, он также используется для:
- Репликации (standby-серверы читают WAL и повторяют изменения),
- Точечного восстановления (PITR),
- Архивации.
Важные нюансы и настройки
- fsync и synchronous_commit:Если отключить fsync в PostgreSQL — атомарность НЕ гарантируется при сбое!
synchronous_commit = off — повышает скорость, но рискует потерей последних транзакций. - Checkpoints:Периодически PostgreSQL записывает «грязные» страницы из буфера на диск,
Это уменьшает объём WAL, который нужно проигрывать при восстановлении. - Не все операции логируются:Например, UNLOGGED таблицы в PostgreSQL не пишут в WAL → быстрее, но данные теряются при сбое.
💡 Практические рекомендации для Java-разработчиков
- Доверяйте СУБД — не пытайтесь реализовать «ручной откат» в коде.
- Используйте транзакции даже для одного UPDATE, если важна целостность.
- Не отключайте fsync в продакшене без экстремальной необходимости.
- Тестируйте сценарии сбоев (например, убивайте процесс СУБД во время транзакции).
- Настройте резервное копирование + WAL-архивацию, чтобы иметь возможность PITR.
✅ Заключение
Да, атомарность в PostgreSQL (и большинстве СУБД) достигается именно за счёт журнала транзакций (WAL).
- Если транзакция не завершена — при восстановлении она откатывается.
- Если транзакция завершена — даже при отсутствии данных на диске — она восстанавливается.
- Это работает автоматически, без участия приложения.
🔌 Именно поэтому вы можете спать спокойно, зная, что отключение электричества не украдёт ваши деньги —
PostgreSQL позаботится об atomicity за вас.