ACID — контракт транзакции

Предпосылки: SQL (BEGIN, COMMIT, ROLLBACK), понятие транзакции как группы операций.

Перевод денег между счетами — два SQL-оператора: списать с одного счёта, зачислить на другой. Простой файл не может гарантировать, что оба действия выполнятся вместе: запись может оборваться посередине. Файл не координирует и параллельный доступ: два процесса одновременно прочитают один и тот же баланс и оба запишут свой результат, не зная друг о друге.

Транзакция — контракт СУБД: группа операций выполняется целиком или не выполняется вообще, параллельные пользователи не ломают данные, результат фиксации переживает сбой. ACID — четыре свойства этого контракта, и каждое закрывает конкретный сценарий отказа.

Сбой посередине операции

Перевод 500 между счетами: UPDATE accounts SET balance = balance - 500 WHERE id = 1, затем UPDATE accounts SET balance = balance + 500 WHERE id = 2. Процесс СУБД падает между ними — первый UPDATE записан, второй нет. Деньги списаны, но не зачислены.

Atomicity (атомарность) — гарантия неделимости: при сбое посередине все изменения транзакции откатываются, как будто её не было. Реализации различаются. Undo-лог записывает предыдущие значения и воспроизводит их при откате. Redo-лог записывает целевые значения и применяет их при восстановлении, а незафиксированные изменения игнорирует. Shadow paging пишет в копию страницы и переключает указатель атомарно. Механизмы разные, контракт один: all-or-nothing.

Атомарность защищает от сбоя во время транзакции. Но что если сбой произойдёт сразу после COMMIT?

Гарантия после COMMIT

СУБД ответила клиенту «COMMIT OK», приложение показало пользователю «перевод выполнен». Через секунду отключилось питание. Если изменения были только в оперативной памяти — данные потеряны, а пользователь уверен, что перевод прошёл.

Durability (надёжность) — гарантия: после COMMIT данные переживут любой сбой. Типичная реализация — write-ahead log (WAL): перед изменением страниц данных СУБД записывает описание изменения в последовательный журнал на диске. При COMMIT — fsync журнала. При восстановлении после сбоя журнал воспроизводится, и все закоммиченные изменения применяются заново. Цена — каждый COMMIT требует fsync, что ограничивает пропускную способность.

Одиночная транзакция теперь защищена от сбоев — и во время выполнения, и после фиксации. Но что если два пользователя одновременно работают с одним и тем же счётом?

Параллельные операции

На счёте 1000. Два параллельных перевода по 500 с этого счёта. Обе транзакции читают баланс 1000, обе вычисляют 1000 − 500 = 500, обе записывают 500. Результат: на счёте 500 вместо 0. Один перевод потерян — классическая аномалия lost update.

Isolation (изоляция) — гарантия: результат параллельных транзакций эквивалентен некоторому последовательному порядку их выполнения. Полная изоляция стоит дорого — она требует либо блокировок (транзакции ждут друг друга), либо откатов при обнаружении конфликтов. Поэтому стандарт SQL определяет уровни изоляции как спектр компромиссов: READ COMMITTED допускает некоторые аномалии, но дёшев; SERIALIZABLE гарантирует эквивалентность последовательному выполнению, но требует больше ресурсов. Конкретные реализации различаются: PostgreSQL использует MVCC (версионирование строк), MySQL InnoDB — MVCC + gap locks, SQL Server — блокировки строк и страниц.

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

Валидность состояния

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

Consistency (согласованность) в контексте ACID — самая узкая буква: база переходит из одного валидного состояния в другое. СУБД проверяет свои ограничения (NOT NULL, UNIQUE, FOREIGN KEY, CHECK) и откатывает транзакцию при нарушении. Бизнес-инварианты, которые нельзя выразить декларативными ограничениями, — ответственность приложения.

Consistency в ACID не следует путать с consistency в CAP-теореме и моделях консистентности: там речь о согласованности копий данных между узлами распределённой системы, здесь — о валидности состояния внутри одного узла. Совпадение термина — историческая случайность.

Цена гарантий

Каждая гарантия — ресурсы и сложность, которые СУБД берёт на себя. Атомарность требует undo/redo-механизмов и места для хранения предыдущих или новых версий данных. Надёжность требует синхронной записи журнала при каждом COMMIT. Изоляция требует либо блокировок (ожидание и потенциальные deadlock’и), либо версионирования (мёртвые версии строк и фоновая очистка). Согласованность требует проверки ограничений при каждой модификации данных. Архитектура конкретной СУБД — история компромиссов: максимум гарантий за минимальную цену.

Не все системы хранения предоставляют полный ACID. Redis, Memcached, многие NoSQL-системы осознанно жертвуют частью гарантий ради производительности и простоты. Выбор между ACID и ослабленными гарантиями (BASE) определяется ценой ошибки: двойное списание денег — инцидент, устаревший счётчик просмотров — допустимо.

Sources