Может ли в транзакциях с уровнем read committed читатель увидеть смешанное состояние данных?
Что проверяют
Понимаете ли вы, на какую единицу распространяется согласованность в Read Committed: на отдельный запрос, а не на транзакцию. Интервьюер ждёт, что вы отличите грязное чтение, которого здесь нет, от смешанного состояния между запросами, которое есть. Следом обычно спрашивают, как это исправить в коде на Go.
Как отвечать
Незакоммиченные данные читатель не увидит, это Read Committed гарантирует. Но смешанное состояние увидеть может:
- внутри одного запроса: в PostgreSQL и в InnoDB каждый оператор читает снимок, снятый в момент его старта. Один
SELECTсJOINвидит согласованную картину; - между запросами в одной транзакции: каждый следующий
SELECTполучает свежий снимок. Прочитали заказ, другая транзакция закоммитила изменение заказа и его позиций, прочитали позиции: заказ старый, позиции новые. Это неповторяемое чтение и фантомы, а в сумме перекос чтения (read skew); UPDATEв PostgreSQL: если строку параллельно изменили, оператор дождётся коммита и перепроверит условие на новой версии, а остальные строки обработает по старому снимку. Документация прямо об этом предупреждает.
Как чинить в Go:
- один запрос вместо нескольких, если данные можно получить через
JOIN; - повышенный уровень изоляции:
db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead, ReadOnly: true}), и все чтения получают один снимок на транзакцию. Для пишущих транзакций на Repeatable Read и Serializable нужен повтор при ошибке40001, в pgx код достаётся из*pgconn.PgError; - явные блокировки:
SELECT ... FOR SHAREилиFOR UPDATE, когда важно, чтобы строки не менялись до конца транзакции.
Где спотыкаются
Отвечают «нет, ведь read committed видит только закоммиченное». Посылка верна, вывод нет: закоммиченные данные разных моментов времени тоже складываются в смешанную картину.
Внутри транзакции вызывают db.QueryContext вместо tx.QueryContext. Запрос уходит на другое соединение из пула, мимо транзакции.
Поднимают уровень до Serializable и не пишут повтор. При первой же конкуренции транзакции начинают падать с ошибкой сериализации, и это выглядит как случайные 500.