Коротко: уровень изоляции транзакций определяет, насколько транзакция видит незавершённые данные других; выше изоляция — меньше аномалий, но дороже.

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

Три классические аномалии

  • Dirty read (грязное чтение) — транзакция читает данные, которые другая ещё не закоммитила (и может откатить).
  • Non-repeatable read (неповторяемое чтение) — повторное чтение той же строки в одной транзакции даёт другое значение, потому что другая транзакция её изменила и закоммитила.
  • Phantom read (фантомное чтение) — повторный запрос по условию возвращает новые строки, появившиеся из-за чужого INSERT.

Четыре уровня изоляции

  • READ UNCOMMITTED — допускает всё, включая грязное чтение. На практике почти не используется.
  • READ COMMITTED — видит только закоммиченные данные (нет грязного чтения), но возможны non-repeatable и phantom. Дефолт в PostgreSQL и большинстве СУБД.
  • REPEATABLE READ — в течение транзакции повторные чтения одной строки стабильны (нет non-repeatable). Дефолт в MySQL/InnoDB. По стандарту допускает фантомы, но InnoDB их во многом гасит за счёт next-key блокировок.
  • SERIALIZABLE — максимальная изоляция: результат как при последовательном выполнении транзакций. Нет ни одной из аномалий, но дороже и выше шанс конфликтов/откатов.

Таблица: что разрешено

Уровень            | Dirty | Non-repeatable | Phantom
-------------------|-------|----------------|--------
READ UNCOMMITTED   |  да   |      да        |  да
READ COMMITTED     |  нет  |      да        |  да
REPEATABLE READ    |  нет  |      нет       |  да*
SERIALIZABLE       |  нет  |      нет       |  нет
(* в InnoDB фантомы во многом предотвращены)

Как задать уровень

-- на сессию
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- на конкретную транзакцию
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
-- ...
COMMIT;

В Doctrine/PDO уровень обычно задают на соединении или через настройки СУБД.

Нюанс MySQL vs PostgreSQL

  • MySQL (InnoDB): дефолт REPEATABLE READ, использует MVCC + next-key locks (меньше фантомов, но возможны gap-локи и дедлоки).
  • PostgreSQL: дефолт READ COMMITTED; REPEATABLE READ реализован как snapshot isolation; SERIALIZABLE — через SSI (Serializable Snapshot Isolation), может откатывать транзакции с ошибкой сериализации — это нужно ловить и ретраить.

Что выбирать на практике

  • READ COMMITTED — разумный дефолт для большинства веб-приложений: дёшево, без грязного чтения.
  • REPEATABLE READ — когда в рамках одной транзакции важна стабильность читаемых данных (отчёты, расчёты по снапшоту).
  • SERIALIZABLE — для критичных инвариантов (финансы, остатки), где недопустимы аномалии; закладывайте ретраи на ошибки сериализации.

Изоляция не заменяет блокировки приложения

Для конкурентных обновлений одной записи (списание баланса, бронирование) одной изоляции мало — используйте SELECT ... FOR UPDATE (пессимистичная блокировка) или версионирование строки (оптимистичная блокировка через version-поле). Изоляция управляет видимостью, а блокировки — порядком конкурентных изменений.

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