Как называется проблема неконсистентных данных при параллельных транзакциях
Что проверяют
Различаете ли вы гонку данных в памяти Go-программы и аномалии параллельных транзакций в базе. Правильный ответ: это аномалии изоляции, и у каждой своё имя. Следом спрашивают про уровни изоляции и про то, как задать нужный уровень из Go.
Как отвечать
Назовите аномалии и уровни, которые от них защищают:
- грязное чтение (dirty read): транзакция видит незакоммиченные изменения чужой;
- неповторяющееся чтение (non-repeatable read): повторный
SELECTтой же строки в одной транзакции возвращает другое значение; - фантомное чтение (phantom read): повторный запрос по условию возвращает другой набор строк;
- потерянное обновление (lost update): две транзакции прочитали баланс 100, каждая записала своё, одно списание исчезло;
- write skew: обе транзакции проверили инвариант по разным строкам и нарушили его вместе, классический пример с дежурными врачами.
Стандарт SQL задаёт четыре уровня: Read Uncommitted, Read Committed, Repeatable Read, Serializable. В PostgreSQL по умолчанию Read Committed, Read Uncommitted ведёт себя как Read Committed, Repeatable Read реализован через снимок и фантомов не допускает, а Serializable работает через SSI и может вернуть ошибку 40001, после которой транзакцию нужно повторить. В MySQL InnoDB по умолчанию Repeatable Read.
В Go уровень задают через db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable}) или pgx.TxOptions{IsoLevel: pgx.Serializable}. Против потерянного обновления хватает атомарного UPDATE, который считает новое значение в самой базе, а не в Go, либо SELECT ... FOR UPDATE, либо оптимистичной блокировки с колонкой версии.
Где спотыкаются
Отвечают «data race» и предлагают sync.Mutex. Мьютекс защищает только память одного процесса, второй экземпляр сервиса его не заметит, а go test -race про базу ничего не знает.
Путают неконсистентность с дедлоком. Взаимоблокировка это отдельная проблема, база обнаруживает её сама и откатывает одну из транзакций.
Включают Serializable и не пишут цикл повтора. Под нагрузкой ошибки сериализации становятся штатными, и без ретраев пользователь получит 500.