Получить доступ

Транзакции, изоляция и блокировки

ACID по буквам, аномалии чтения, уровни изоляции, MVCC и оптимистичные/пессимистичные блокировки.

ACID по буквам

Atomicity — «всё или ничего»: транзакция применяется целиком либо откатывается. Consistency — переход из одного валидного состояния в другое без нарушения инвариантов и ограничений. Isolation — параллельные транзакции влияют друг на друга контролируемо. Durability — после commit изменения переживут сбой (за счёт журнала/WAL).

Частая путаница: Consistency из ACID (соблюдение ограничений одной БД) — это не eventual consistency из распределённых систем (репликации со временем сходятся).

Аномалии параллельного доступа

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

Уровни изоляции — это ровно про то, какие из этих аномалий разрешены.

Уровни изоляции

По стандарту SQL: READ UNCOMMITTED допускает все три аномалии; READ COMMITTED убирает dirty read; REPEATABLE READ убирает ещё и non-repeatable; и только SERIALIZABLE убирает все три, включая phantom.

Чем выше изоляция, тем меньше аномалий, но выше цена: больше блокировок/конфликтов и ниже параллелизм.

Любимая ловушка: «по стандарту» ≠ «в жизни». В PostgreSQL уровень REPEATABLE READ реализован как snapshot isolation и фактически не допускает и phantom (то есть строже стандарта), а READ UNCOMMITTED ведёт себя как READ COMMITTED (грязного чтения в PostgreSQL нет вообще). В Oracle уровней REPEATABLE READ и READ UNCOMMITTED просто нет — есть READ COMMITTED (дефолт) и SERIALIZABLE. Поэтому называйте и стандарт, и поведение конкретной СУБД.

Стандартных аномалий не «ровно три». Есть ещё lost update (потерянное обновление: две транзакции читают значение, обновляют и затирают чужой результат — read-modify-write без блокировки/версии) и write skew (две транзакции читают общий набор, каждая по отдельности проходит проверку, а вместе нарушают инвариант). Lost update лечится SELECT ... FOR UPDATE или оптимистичной блокировкой по версии; write skew снимается только настоящей сериализуемостью (в PostgreSQL — SSI, Serializable Snapshot Isolation).

Матрица уровней изоляции и аномалий: для каждого уровня (Read Uncommitted … Serializable) показано, какие аномалии (грязное, неповторяемое, фантомное чтение) возможны, а какие предотвращены.
Чем выше уровень изоляции, тем больше аномалий он предотвращает — ценой параллелизма.

MVCC: чтение без блокировок

Многие СУБД (PostgreSQL, InnoDB, Oracle) используют MVCC: вместо блокировки на чтение они хранят версии строк, и запрос видит согласованный снимок данных. Когда снимок берётся, зависит от уровня изоляции: на READ COMMITTED — заново для каждого запроса, на REPEATABLE READ — один раз на всю транзакцию.

Поэтому читатели не блокируют писателей и наоборот. Цена — накопление старых версий, которые нужно вычищать (vacuum/purge), и нюансы вроде gap-locks для предотвращения phantom в REPEATABLE READ у InnoDB.

Оптимистичные и пессимистичные блокировки

Пессимистичная блокировка захватывает строки заранее (SELECT … FOR UPDATE), и другие ждут. Подходит, когда конфликты часты и повтор операции дорог.

Оптимистичная не блокирует, а проверяет версию при записи: если version изменился — конфликт, операция повторяется. Подходит при редких конфликтах. В JPA это поле @Version.

// JPA optimistic locking
@Entity
class Account {
    @Id Long id;
    @Version long version; // проверяется и инкрементируется при UPDATE
    long balance;
}

Deadlock

Deadlock возникает, когда транзакции захватывают ресурсы во ВСТРЕЧНОМ порядке: первая держит строку A и ждёт B, вторая держит B и ждёт A. Ждать они будут вечно, поэтому СУБД обнаруживает цикл ожидания и принудительно откатывает одну из транзакций (жертву) с ошибкой deadlock detected.

Классический пример — перевод денег между счетами: два параллельных перевода 1→2 и 2→1 блокируют счета в разном порядке. Лечится не «побольше изоляции», а единым порядком захвата: всегда блокировать счета по возрастанию id, тогда цикла не возникнет.

Остальные меры снижают вероятность, а не исключают её: короткие транзакции (меньше окно удержания блокировок), точечные блокировки вместо широких диапазонов, отсутствие пользовательского ввода и внешних HTTP-вызовов внутри транзакции. Поскольку deadlock в принципе возможен, приложение должно уметь ПОВТОРИТЬ отброшенную транзакцию — это нормальный штатный сценарий, а не сбой.

-- обе транзакции берут строки в одном порядке (по возрастанию id)
begin;
  select * from accounts where id in (:from, :to)
  order by id for update;   -- порядок захвата предсказуем → цикла нет
  update accounts set balance = balance - :amt where id = :from;
  update accounts set balance = balance + :amt where id = :to;
commit;

Как отвечать на собеседовании

Не пересказывайте ACID по буквам без связок — сразу разведите две «C»: Consistency из ACID (соблюдение ограничений одной БД) и eventual consistency из распределённых систем. Это первое, что отличает подготовленного кандидата.

Дальше ведите линию «аномалия → уровень изоляции»: dirty read снимается READ COMMITTED, non-repeatable — REPEATABLE READ, phantom — SERIALIZABLE. Затем добавьте то, что знают немногие: аномалий не три, есть ещё lost update (лечится FOR UPDATE или @Version) и write skew (снимается только настоящей сериализуемостью).

Обязательно оговоритесь, что стандарт и реализация расходятся: в PostgreSQL REPEATABLE READ — это snapshot isolation, который строже стандарта, а READ UNCOMMITTED ведёт себя как READ COMMITTED. Такая оговорка показывает практический опыт.

Про MVCC скажите суть в одной фразе: читатели не блокируют писателей, потому что транзакция видит снимок данных, — и назовите цену: старые версии нужно вычищать (vacuum), а долгие транзакции мешают этому и раздувают таблицы.

Выбор блокировки объясняйте через частоту конфликтов: редкие — оптимистичная по версии с повтором операции, частые и дорогие для повтора — пессимистичная SELECT ... FOR UPDATE. Завершите deadlock'ом: единый порядок захвата строк плюс готовность приложения повторить откаченную транзакцию.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем