По умолчанию в PostgreSQL — Read Committed. Repeatable Read здесь это snapshot isolation. Serializable ловит write skew ценой ретраев по serialization_failure.
PostgreSQL postgres собеседование
Изоляция отвечает на вопрос: что может увидеть и изменить параллельная транзакция, пока ваша ещё не закоммитила. Это не то же самое, что MVCC «вообще», и не то же самое, что row lock. MVCC даёт снимок (несколько версий одной строки). Уровень изоляции решает, когда снимок берётся и какие конфликты считаются ошибкой.
По умолчанию в PostgreSQL — Read Committed. Это важно назвать сразу.
Аномалии лучше называть своими именами. Dirty read — видите незакоммиченное; Postgres такого не отдаёт даже на Read Committed. Non-repeatable read — повтор того же SELECT в одной транзакции видит чужой commit. Phantom — появились или пропали строки, подходящие под предикат. Serialization anomaly, она же write skew: каждая транзакция по своему снимку корректна, вместе они ломают инвариант.
Пример. Два врача. Оба читают «дежурных ≥ 1». Оба решают, что могут уйти. Оба пишут «я не дежурю». Каждая транзакция не трогала строку другой. Вместе дежурных не осталось. Это write skew.
Уровень всплывает, когда два процесса читают «достаточно мест» или «баланс ещё положительный» и оба принимают решение писать. Один SELECT в autocommit эту тонкость не показывает. Нужна явная транзакция из нескольких чтений и записи.
Read Committed
Уровень по умолчанию. Каждый SQL-оператор видит снимок на момент своего старта. Два SELECT в одной транзакции могут разойтись: между ними сосед закоммитил. Для большинства веб-запросов этого хватает: хендлер короткий, повторное чтение той же строки редко является инвариантом.
SELECT … FOR UPDATE на Read Committed сериализует конкретные найденные строки, не предикат. Строки, которых ещё нет, этот лок не защищает: два INSERT «единственного» слота возможны, пока нет UNIQUE.
Repeatable Read — snapshot
Снимок берётся на момент первого statement и держится до конца транзакции. В PostgreSQL это snapshot isolation (SI), а не «как REPEATABLE READ в учебнике MySQL». Классических фантомов на чтении нет: вы не увидите строки, которых не было в снимке. Write skew при этом возможен: обе транзакции читают пересекающиеся наборы, пишут в разные строки, инвариант «хотя бы один дежурный» падает.
Если сосед успел изменить строку, которую вы потом хотите обновить, Postgres на RR/SI может дать could not serialize access due to concurrent update. Это не Serializable целиком, а конфликт версий на конкретной строке.
Serializable
SSI (Serializable Snapshot Isolation) отслеживает опасные rw-зависимости между транзакциями. Если вместе они дали бы результат, невозможный при последовательном выполнении, одна получает serialization_failure (SQLSTATE 40001). Клиент обязан ретраить идемпотентно. Под нагрузкой «поставили SERIALIZABLE и забыли» превращается в каскад ошибок, а не в магическую корректность.
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT balance FROM accounts WHERE id = 1;
-- другая сессия успевает COMMIT UPDATE того же ряда
SELECT balance FROM accounts WHERE id = 1; -- RR: то же значение; RC: уже новое
COMMIT;
На Repeatable Read повторный SELECT видит тот же снимок. На Read Committed второй SELECT уже может показать чужой commit.
Лечение write skew — SERIALIZABLE, явный лок на агрегат, constraint «хотя бы один дежурный» или материализованная строка-счётчик, которую оба обновляют. RC и RR это пропускают.
Типичная ошибка
Повысить isolation до SERIALIZABLE «на всякий случай» без ретрая 40001 на клиенте. Вторая ошибка — считать, что FOR UPDATE на Read Committed заменяет Repeatable Read: вы сериализуете найденные строки, а не предикат, и не закрываете write skew на разных рядах.
На собеседовании
Default — Read Committed. Repeatable Read в Postgres — snapshot isolation, не копия MySQL. Write skew: две корректные транзакции ломают инвариант. Лечится SSI либо локом или constraint на сам инвариант.