MidТеория2 min

ACID — свойства транзакций

Atomicity, Consistency, Isolation, Durability: что это значит на практике и как проверяется на интервью

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 данные сохранены навсегда, даже при последующем сбое системы.

Как реализуется

  1. WAL (Write-Ahead Log) — каждое изменение записывается в журнал на диск до применения к данным
  2. fsync — гарантия, что данные физически записаны на диск, а не только в кэш ОС
  3. 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, делают несогласованные изменения в нескольких транзакциях.

Проверь себя

MySQL MyISAM не поддерживает ACID. Какое конкретное свойство отсутствует в первую очередь?

Какое свойство ACID частично является ответственностью приложения, а не только СУБД?

Параметр synchronous_commit = off в PostgreSQL ослабляет какое свойство ACID?

Что такое Dirty Read?

Банк выполняет перевод: списывает 500₽ со счёта А и зачисляет на счёт Б. Сервер падает после списания, но до зачисления. Какое свойство ACID предотвращает потерю денег?