Как работает изоляция Repeatable Read и что такое аномалия сериализации
Что проверяют
Различаете ли вы уровень изоляции из стандарта SQL и то, как его реализует конкретная база. Главный маркер: знаете ли вы, что Repeatable Read в PostgreSQL не пускает фантомы, но пропускает write skew. Следом просят привести пример аномалии и сказать, как с ней жить в Java-коде.
Как отвечать
По стандарту Repeatable Read запрещает грязное и неповторяемое чтение, а фантомы допускает. На практике:
- PostgreSQL: транзакция работает со снимком, снятым на первом запросе. Фантомов нет, повторное чтение даёт то же самое. Если две транзакции меняют одну строку, вторая получит
could not serialize access due to concurrent updateс SQLSTATE40001. Уровень по умолчанию в PostgreSQL при этом Read Committed; - MySQL InnoDB: Repeatable Read стоит по умолчанию. Обычный
SELECTчитает снимок, аSELECT ... FOR UPDATEиUPDATEвидят последнюю версию и ставят next-key блокировки, поэтому внутри одной транзакции они могут разойтись с обычным чтением.
Аномалия сериализации это результат параллельных транзакций, который невозможно получить ни при одном последовательном порядке их выполнения. Классика: в больнице должен дежурить хотя бы один врач, сейчас дежурят двое. Две транзакции одновременно видят «дежурных двое», и каждая снимает с дежурства своего врача. Строки разные, конфликта записи нет, каждый снимок был корректным, а в итоге не дежурит никто. Это write skew, и Repeatable Read в PostgreSQL его пропускает.
Лечится тремя способами: уровень Serializable (в PostgreSQL это SSI, он обрывает одну из транзакций с 40001), явная блокировка через SELECT ... FOR UPDATE или ограничение в самой схеме. В Spring уровень задают через @Transactional(isolation = Isolation.SERIALIZABLE), и к нему обязательно нужен повтор транзакции при 40001.
Где спотыкаются
Называют фантомы аномалией сериализации. Фантом лишь частный случай, а write skew возникает и без появления новых строк.
Отвечают только по стандарту, без оговорки о СУБД. У PostgreSQL и MySQL при одном названии уровня поведение разное.
Включают Serializable и не пишут повтор. Тогда под нагрузкой пользователи видят ошибки, которые при повторной попытке прошли бы.