В каких ситуациях нужен уровень изоляции SERIALIZABLE
Что проверяют
Понимаете ли вы, от какой аномалии защищает SERIALIZABLE и чем за это платить. Следом спрашивают, что будет с транзакцией при конфликте и как это обработать в коде.
Как отвечать
SERIALIZABLE гарантирует, что результат параллельных транзакций совпадает с каким-то их последовательным выполнением. Главное, что он закрывает сверх REPEATABLE READ, это write skew: две транзакции читают одни данные, принимают решение по прочитанному и пишут в разные строки. По отдельности каждая корректна, вместе они ломают инвариант.
Где это нужно:
- инвариант на несколько строк: на дежурстве должен остаться хотя бы один врач, а двое одновременно снимаются, видя друг друга в графике;
- вставка по условию без уникального ключа: «забронировать слот, если в этом интервале нет пересечений»;
- лимит по сумме строк: общий расход по нескольким счетам не должен превысить порог.
В PostgreSQL уровень по умолчанию READ COMMITTED, а SERIALIZABLE реализован как Serializable Snapshot Isolation. Лишних блокировок чтения нет: база следит за зависимостями и при опасной ситуации обрывает одну из транзакций с ошибкой 40001 (serialization_failure). В MySQL InnoDB, наоборот, SERIALIZABLE работает на блокировках: обычный SELECT внутри транзакции превращается в SELECT ... FOR SHARE.
Отсюда обязательное условие: повторять транзакцию целиком. В Python это цикл или tenacity вокруг всей функции, ловящий psycopg.errors.SerializationFailure. Уровень задаётся в SQLAlchemy параметром isolation_level="SERIALIZABLE" у движка или через execution_options.
Назовите и дешёвые альтернативы: SELECT ... FOR UPDATE, уникальные и exclusion-ограничения, атомарный UPDATE ... WHERE balance >= :amount.
Где спотыкаются
Включают SERIALIZABLE и не пишут повтор. Под нагрузкой пользователи получают ошибки сериализации, и уровень откатывают.
Предлагают его для перевода денег между двумя счетами. Там хватает блокировки строк или условного UPDATE, а SERIALIZABLE добавляет откаты без пользы.
Не знают, что в PostgreSQL защита действует только между транзакциями уровня SERIALIZABLE. Если часть кода пишет в те же таблицы на READ COMMITTED, гарантии нет.