ACID — аббревиатура из четырёх свойств, которые гарантируют надёжность транзакций в реляционных базах данных. Понимание ACID — обязательное требование на любом backend-интервью.
Что такое транзакция
Транзакция — это единица работы с базой данных, которая выполняется как единое целое: либо полностью, либо не выполняется вообще.
-- Classic example: bank transfer
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;
Если между двумя UPDATE произойдёт сбой, деньги не должны «пропасть». Именно для этого нужен ACID.
A — Atomicity (Атомарность)
Определение: транзакция выполняется полностью или не выполняется вообще. Не существует «частичного» выполнения.
Как работает
Если транзакция содержит 10 операций и 7-я упала с ошибкой — база откатывает все предыдущие 6 операций к исходному состоянию.
BEGIN;
INSERT INTO orders (user_id, total) VALUES (42, 5000); -- OK
INSERT INTO order_items (order_id, product_id) VALUES (1, 7); -- OK
UPDATE inventory SET qty = qty - 1 WHERE product_id = 7; -- ERROR: qty = 0
-- All changes rolled back automatically
ROLLBACK; -- or automatic if error occurs
Механизм реализации
PostgreSQL и большинство СУБД используют WAL (Write-Ahead Log) — журнал, в который записываются все изменения до их применения к данным. При сбое база воспроизводит или откатывает операции по журналу.
| Состояние | Что происходит |
|---|---|
| Транзакция завершается COMMIT | WAL записывает COMMIT, изменения применяются |
| Транзакция падает с ошибкой | WAL откатывает незавершённые изменения |
| Сбой сервера во время транзакции | При рестарте WAL восстанавливает состояние |
Частый вопрос на интервью
«Что произойдёт, если питание пропадёт в момент COMMIT?»
Ответ: зависит от того, записан ли COMMIT в WAL. Если WAL зафлашен на диск — транзакция считается завершённой и будет воспроизведена при рестарте. Если нет — откатится.
C — Consistency (Согласованность)
Определение: транзакция переводит базу из одного валидного состояния в другое валидное. Все бизнес-правила и ограничения соблюдаются.
Что входит в «согласованность»
- Ограничения целостности (constraints): NOT NULL, UNIQUE, CHECK, FOREIGN KEY
- Триггеры и правила, заданные в схеме
- Бизнес-правила, реализованные в коде приложения (баланс не может быть отрицательным)
-- Consistency violation attempt
BEGIN;
UPDATE accounts SET balance = -500 WHERE id = 1;
-- If there's a CHECK constraint: CHECK (balance >= 0)
-- PostgreSQL raises: ERROR: new row violates check constraint "accounts_balance_check"
ROLLBACK; -- automatic
Важный нюанс: C — ответственность приложения
В отличие от A, I, D — которые гарантируются СУБД, Consistency частично зависит от приложения. СУБД обеспечивает только те ограничения, которые явно заданы в схеме. Бизнес-правила вида «заказ не может быть оформлен, если товара нет на складе» — ответственность прикладного кода.
I — Isolation (Изолированность)
Определение: параллельные транзакции не видят промежуточных результатов друг друга. Каждая транзакция выглядит так, будто выполняется одна.
Три классических проблемы
1. Dirty Read (Грязное чтение)
Транзакция B читает данные, изменённые транзакцией A, которая ещё не завершилась (не сделала COMMIT).
T1: BEGIN; UPDATE users SET name = 'Vася' WHERE id = 1;
T2: SELECT name FROM users WHERE id = 1; -- reads 'Vася' (dirty!)
T1: ROLLBACK; -- T1 cancelled, but T2 already used wrong data
2. Non-Repeatable Read (Неповторяемое чтение)
Транзакция читает одну строку дважды и получает разные результаты, потому что другая транзакция изменила её между двумя чтениями.
T1: SELECT balance FROM accounts WHERE id = 1; -- returns 1000
T2: UPDATE accounts SET balance = 500 WHERE id = 1; COMMIT;
T1: SELECT balance FROM accounts WHERE id = 1; -- returns 500 (different!)
3. Phantom Read (Фантомное чтение)
Транзакция выполняет один и тот же запрос дважды, но получает разный набор строк, потому что другая транзакция добавила или удалила строки.
T1: SELECT COUNT(*) FROM orders WHERE status = 'new'; -- returns 5
T2: INSERT INTO orders (status) VALUES ('new'); COMMIT;
T1: SELECT COUNT(*) FROM orders WHERE status = 'new'; -- returns 6 (phantom!)
Уровни изоляции
SQL стандарт определяет четыре уровня изоляции, которые по-разному защищают от этих проблем. Подробно — в следующей статье.
| Проблема | READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE |
|---|---|---|---|---|
| Dirty Read | ✗ | ✓ | ✓ | ✓ |
| Non-Repeatable Read | ✗ | ✗ | ✓ | ✓ |
| Phantom Read | ✗ | ✗ | ✗ | ✓ |
✓ = защищено, ✗ = не защищено
D — Durability (Долговечность)
Определение: после успешного COMMIT данные сохранены навсегда, даже при последующем сбое системы.
Как реализуется
- WAL (Write-Ahead Log) — каждое изменение записывается в журнал на диск до применения к данным
- fsync — гарантия, что данные физически записаны на диск, а не только в кэш ОС
- Checkpoints — периодический сброс всех изменений из памяти на диск
-- After this COMMIT, data is guaranteed to survive any crash
BEGIN;
INSERT INTO critical_events (type, occurred_at) VALUES ('payment', NOW());
COMMIT; -- WAL flushed to disk, durability guaranteed
Компромисс: производительность vs надёжность
Параметр synchronous_commit в PostgreSQL позволяет пожертвовать долговечностью ради скорости:
-- Default: wait for WAL to be written to disk before returning
SET synchronous_commit = on;
-- Faster, but last few transactions may be lost on crash
SET synchronous_commit = off;
synchronous_commit = off применяется, например, для логирования, где потеря нескольких последних строк при сбое некритична.
ACID в разных СУБД
| СУБД | ACID | Примечания |
|---|---|---|
| PostgreSQL | ✓ | Полная поддержка ACID, MVCC |
| MySQL InnoDB | ✓ | Полная поддержка ACID |
| MySQL MyISAM | ✗ | Нет транзакций, нет FK |
| SQLite | ✓ | WAL-mode для конкурентности |
| MongoDB | Частично | Транзакции добавлены в 4.0 (только replica set) |
| Redis | Частично | MULTI/EXEC — атомарность, но не полный ACID |
| Cassandra | ✗ | BASE-модель, eventual consistency |
BASE — альтернатива ACID
NoSQL системы часто используют BASE вместо ACID:
- Basically Available — система всегда доступна (возможны устаревшие данные)
- Soft state — состояние может меняться даже без входящих данных
- Eventually consistent — система в конечном счёте придёт в согласованное состояние
BASE жертвует согласованностью ради доступности и скорости. Пример: лайки на YouTube — допускается небольшая рассинхронизация между серверами.
Типичные вопросы на интервью
Q: «Объясните ACID своими словами»
Хороший ответ: «ACID — это набор из четырёх гарантий для транзакций. Атомарность — всё или ничего. Согласованность — данные всегда в валидном состоянии согласно правилам схемы. Изолированность — параллельные транзакции не мешают друг другу. Долговечность — после COMMIT данные не потеряются. Классический пример — банковский перевод: деньги не должны пропасть при сбое между списанием и зачислением.»
Q: «Чем ACID отличается от BASE?»
ACID — строгие гарантии, используются в реляционных БД, жертвуют производительностью и горизонтальным масштабированием. BASE — слабые гарантии, eventual consistency, используются в NoSQL для высокой доступности и масштабируемости.
Q: «Может ли ACID нарушиться?»
Технически — нет, если СУБД работает корректно. Но разработчики часто нарушают C (Consistency) на уровне приложения — не добавляют необходимые constraints, делают несогласованные изменения в нескольких транзакциях.