Что такое грязное чтение, фантомное чтение и неповторяемое чтение в транзакциях
Что проверяют
Знаете ли вы аномалии чтения и умеете ли связать их с уровнями изоляции и с кодом сервиса. Для бэкендера это ещё и вопрос, что делать, когда база отказалась зафиксировать транзакцию.
Как отвечать
Три аномалии по возрастанию тонкости:
- грязное чтение: транзакция видит изменения другой, ещё не зафиксированной. Если та откатится, прочитанного никогда не существовало;
- неповторяемое чтение: одна и та же строка, прочитанная внутри транзакции дважды, даёт разные значения, потому что между чтениями её изменили и зафиксировали;
- фантомное чтение: повторный запрос по тому же условию возвращает другой набор строк, потому что рядом добавили или удалили подходящие.
Дальше уровни изоляции, но с оговоркой про конкретную СУБД: стандарт описывает предельно допустимые аномалии, а движки дают больше гарантий. В PostgreSQL грязного чтения нет ни на одном уровне, а Repeatable Read работает как снимок и закрывает в том числе фантомы. Serializable там не блокирует, а отменяет конфликтующую транзакцию.
В коде это выглядит так:
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})и обязательно требует цикла повтора по коду ошибки сериализации.
Где спотыкаются
Пересказывают таблицу стандарта как свойство своей базы. Уровень с одним и тем же названием в разных движках даёт разные гарантии, и ответ без указания СУБД засчитывается наполовину.
Поднимают изоляцию и не добавляют повтор. Под нагрузкой транзакции начинают отменяться, ошибка доходит до пользователя, и команда возвращает прежний уровень, решив, что новый сломан.
Забывают про аномалии записи. Потерянное обновление и перекос записи изоляцией снимка не закрываются: счётчик надо менять одним запросом, взаимозависимую проверку делать через блокировку строк или на Serializable. И транзакцию нельзя держать открытой на время обращения к внешнему сервису: соединение из пула занято, блокировки живут лишние секунды.