Найти в Дзене
Записки о Java

Atomicity в PostgreSQL и других СУБД: как база данных гарантирует «всё или ничего», даже при отключении электричества

Стек: Java 11, PostgreSQL 12+, общие принципы СУБД
Цель: понять, как достигается свойство Atomicity, что такое WAL (Write-Ahead Logging), и как СУБД восстанавливается после аварийного отключения. 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). Все современные СУБД (PostgreSQL, Oracle, MySQL/InnoDB, SQL Server) используют один и тот же фундаментальный механизм: Write-Ahead Logging (WAL) — «запись в журнал до записи на диск». Любое изменение д
Оглавление
Рисунок: ACID для реляционных СУБД
Рисунок: ACID для реляционных СУБД

Стек: 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.

⚡ Что происходит при отключении питания?

Представим ваш сценарий:

  1. Начата транзакция перевода.
  2. Выполнено: UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    → Создана WAL-запись для этого изменения.
  3. Отключается электричество — до UPDATE ... +100 и COMMIT.

При перезапуске PostgreSQL выполняет recovery (восстановление):

  1. Читает WAL-журнал с последней контрольной точки (checkpoint).
  2. Находит незавершённые транзакции (те, у которых нет записи commit).
  3. Для каждой такой транзакции — выполняет откат (undo):Использует WAL-записи, чтобы восстановить предыдущее состояние строк,
    Фактически
    «перематывает» изменения назад.
Результат:
Счёт
не уменьшился на 100,
потому что транзакция
никогда не была завершена.
🔁 Это называется rollback on recovery.

🔄 А если транзакция была COMMIT-нута, но данные не записаны?

Сценарий:

  1. Обе операции выполнены.
  2. COMMIT отправлен — WAL-запись commit сброшена на диск.
  3. Электричество отключилось до того, как изменения попали в файлы данных.

При восстановлении:

  1. PostgreSQL видит в WAL: «транзакция XID завершена».
  2. Но в файлах данных — старые значения.
  3. Тогда выполняется redo (повтор):Все WAL-записи для этой транзакции применяются к данным,
    Состояние базы
    доводится до консистентного.
Результат:
Перевод
успешно завершён,
даже если данные физически не были записаны до сбоя.

📊 WAL: не только для Atomicity

Хотя WAL основа для Atomicity и Durability, он также используется для:

  • Репликации (standby-серверы читают WAL и повторяют изменения),
  • Точечного восстановления (PITR),
  • Архивации.

Важные нюансы и настройки

  1. fsync и synchronous_commit:Если отключить fsync в PostgreSQL — атомарность НЕ гарантируется при сбое!
    synchronous_commit = off — повышает скорость, но
    рискует потерей последних транзакций.
  2. Checkpoints:Периодически PostgreSQL записывает «грязные» страницы из буфера на диск,
    Это уменьшает объём WAL, который нужно проигрывать при восстановлении.
  3. Не все операции логируются:Например, UNLOGGED таблицы в PostgreSQL не пишут в WAL → быстрее, но данные теряются при сбое.

💡 Практические рекомендации для Java-разработчиков

  1. Доверяйте СУБД — не пытайтесь реализовать «ручной откат» в коде.
  2. Используйте транзакции даже для одного UPDATE, если важна целостность.
  3. Не отключайте fsync в продакшене без экстремальной необходимости.
  4. Тестируйте сценарии сбоев (например, убивайте процесс СУБД во время транзакции).
  5. Настройте резервное копирование + WAL-архивацию, чтобы иметь возможность PITR.

✅ Заключение

Да, атомарность в PostgreSQL (и большинстве СУБД) достигается именно за счёт журнала транзакций (WAL).
  • Если транзакция не завершена — при восстановлении она откатывается.
  • Если транзакция завершена — даже при отсутствии данных на диске — она восстанавливается.
  • Это работает автоматически, без участия приложения.
🔌 Именно поэтому вы можете спать спокойно, зная, что отключение электричества не украдёт ваши деньги
PostgreSQL позаботится об atomicity за вас.