Что такое ACID? Объясни каждую букву на конкретном примере с банковским переводом.
ACID: Atomicity — все операции выполняются или ни одна; Consistency — данные соответствуют ограничениям до и после; Isolation — параллельные транзакции не видят промежуточных состояний друг друга; Durability — зафиксированные данные переживают сбой.
ACID: четыре свойства транзакций
Аббревиатура ACID описывает гарантии, которые СУБД предоставляет приложению при работе с транзакциями. Разберём каждое свойство на примере банковского перевода: Алиса переводит 1000 рублей Бобу.
A — Atomicity (Атомарность)
Транзакция — неделимая единица: либо все операции выполняются, либо ни одна. Если после списания с Алисы произойдёт сбой сервера, PostgreSQL при старте откатит незавершённую транзакцию по WAL-журналу, и баланс Алисы вернётся к исходному значению.
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1; -- Алиса
-- Если здесь упадёт сервер — следующая строка не выполнится
UPDATE accounts SET balance = balance + 1000 WHERE id = 2; -- Боб
COMMIT;
-- При ROLLBACK или сбое оба UPDATE отменяются
C — Consistency (Согласованность)
До начала и после завершения транзакции данные должны удовлетворять всем ограничениям и инвариантам. В нашем примере сумма балансов всех счетов должна оставаться неизменной. PostgreSQL проверяет CHECK-ограничения, FOREIGN KEY и UNIQUE в момент фиксации (или немедленно, для NOT DEFERRABLE).
ALTER TABLE accounts ADD CONSTRAINT balance_non_negative CHECK (balance >= 0);
-- Попытка перевести больше, чем есть на счёте:
BEGIN;
UPDATE accounts SET balance = balance - 5000 WHERE id = 1; -- нарушит CHECK
COMMIT;
-- ERROR: new row for relation "accounts" violates check constraint "balance_non_negative"
-- Транзакция автоматически откатится
I — Isolation (Изолированность)
Параллельные транзакции не должны мешать друг другу. PostgreSQL реализует изоляцию через MVCC (Multi-Version Concurrency Control): каждая транзакция видит снапшот данных на момент начала. Уровни изоляции регулируют, какие аномалии допустимы:
- READ COMMITTED (по умолчанию) — каждый оператор видит уже зафиксированные данные; возможны non-repeatable reads.
- REPEATABLE READ — снапшот фиксируется на начало транзакции; исключает non-repeatable reads, но допускает phantom reads (в PostgreSQL — нет, реализован строже стандарта).
- SERIALIZABLE — полная эмуляция последовательного выполнения транзакций через SSI.
-- Транзакция 1 читает баланс Алисы
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE id = 1; -- видит 5000
-- В это время Транзакция 2 списывает 1000 и COMMIT
SELECT balance FROM accounts WHERE id = 1; -- всё ещё видит 5000 (снапшот!)
COMMIT;
D — Durability (Долговечность)
После COMMIT данные сохранены даже при сбое питания или краше ОС. PostgreSQL использует WAL (Write-Ahead Log): сначала запись попадает в журнал на диске (fsync), затем в heap. При старте после краша база воспроизводит WAL и восстанавливает все зафиксированные транзакции.
-- Параметры, влияющие на Durability (не менять в production без понимания!)
SHOW synchronous_commit; -- on (по умолчанию) — fsync WAL до ответа клиенту
SHOW fsync; -- on (по умолчанию) — гарантирует сброс на диск
-- Для некритичных данных (логи) можно ослабить:
SET synchronous_commit = off; -- риск потери последних ~200 мс транзакций при краше
Полный пример перевода с проверкой инвариантов
DO $$
DECLARE
from_balance numeric;
BEGIN
-- Блокируем строки в порядке id во избежание deadlock
SELECT balance INTO from_balance
FROM accounts
WHERE id = 1
FOR UPDATE;
IF from_balance < 1000 THEN
RAISE EXCEPTION 'Insufficient funds: balance is %', from_balance;
END IF;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;
END;
$$;
Подводные камни
- Согласованность — ответственность приложения, а не только СУБД: PostgreSQL не знает о бизнес-инварианте «сумма балансов должна быть равна X»; если не написать CHECK или не проверить в коде, инвариант нарушится.
- READ COMMITTED допускает non-repeatable reads: два SELECT в одной транзакции могут дать разные результаты, если между ними кто-то зафиксировал изменение. Для финансовых расчётов используйте REPEATABLE READ или SERIALIZABLE.
- SERIALIZABLE в PostgreSQL реализован через SSI и может вызывать serialization failures (ERROR 40001) — приложение должно уметь повторять транзакцию.
- synchronous_commit = off ослабляет Durability: при краше могут быть потеряны последние ~200 мс зафиксированных транзакций — клиент получил COMMIT, но данные не выжили.
- Долгие открытые транзакции блокируют VACUUM и накапливают мёртвые версии строк (bloat), что деградирует производительность всей БД.
- Явный SELECT ... FOR UPDATE необходим для предотвращения race condition при проверке баланса: без блокировки строки два параллельных перевода могут оба «увидеть» достаточный баланс и оба списать деньги.
- SAVEPOINT позволяет частично откатить транзакцию, но не нарушает Atomicity — внешний COMMIT всё равно фиксирует всё, что осталось после ROLLBACK TO SAVEPOINT.
Common mistakes
- Объяснять ACID только расшифровкой.
- Думать, что ACID сам решает все race conditions.
- Не связывать durability с WAL.
What the interviewer is testing
- Просит пример rollback.
- Проверяет isolation на конкурентном списании.
- Уточняет роль WAL после commit.