> Что такое грязное чтение, фантомное чтение и неповторяемое чтение в транзакциях (Go)
Уровень: junior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Wildberries
Стек: Go
> Пример ответа
Короткий ответ
Грязное чтение - чтение данных, записанных незавершённой транзакцией. Неповторяемое чтение - ситуация, когда повторный SELECT в одной транзакции возвращает разные строки из-за commit другой транзакции. Фантомное чтение - повторный SELECT возвращает новые строки, добавленные другой транзакцией. Отличие: неповторяемое чтение касается изменения существующих строк, фантомное - появления новых. Уровни изоляции в PostgreSQL решают эти проблемы: READ COMMITTED допускает все три, REPEATABLE READ исключает грязное и неповторяемое, но допускает фантомное.
Подробное объяснение
Три аномалии чтения возникают при параллельном выполнении транзакций, когда одна транзакция читает данные, которые другая изменяет.
Грязное чтение (dirty read) - транзакция A читает строку, которую транзакция B изменила, но ещё не закоммитила. Если B откатится, A работала с данными, которых никогда не существовало. Это самая опасная аномалия, потому что данные могут быть полностью невалидными.
Неповторяемое чтение (non-repeatable read) - транзакция A дважды выполняет один и тот же SELECT. Между этими запросами транзакция B коммитит изменение существующей строки (UPDATE или DELETE). Первый SELECT вернул старое значение, второй - новое. Данные валидны, но результат нестабилен внутри одной транзакции.
Фантомное чтение (phantom read) - транзакция A выполняет SELECT с условием (например, WHERE price > 100). Между двумя выполнениями транзакция B коммитит INSERT новой строки, удовлетворяющей условию. Второй SELECT возвращает дополнительную строку - "фантом". Отличие от неповторяемого чтения: меняется не содержимое строк, а их набор.
Стандарт SQL определяет четыре уровня изоляции:
| Уровень | Грязное чтение | Неповторяемое чтение | Фантомное чтение |
|---|---|---|---|
| READ UNCOMMITTED | возможно | возможно | возможно |
| READ COMMITTED | нет | возможно | возможно |
| REPEATABLE READ | нет | нет | возможно |
| SERIALIZABLE | нет | нет | нет |
Важно: в PostgreSQL READ UNCOMMITTED ведёт себя как READ COMMITTED - грязное чтение невозможно на любом уровне. В PostgreSQL REPEATABLE READ также исключает фантомное чтение благодаря snapshot isolation, но это деталь реализации, не гарантия стандарта.
На практике
В Go с драйвером database/sql и pgx уровень изоляции задаётся при старте транзакции. По умолчанию PostgreSQL использует READ COMMITTED - это разумный выбор для большинства приложений: каждая команда видит последний закоммиченный snapshot.
Практические рекомендации:
- Если транзакция выполняет несколько SELECT и ожидает стабильный результат - используй REPEATABLE READ.
- Если нужна полная гарантия отсутствия фантомов - SERIALIZABLE, но будь готов к ошибкам сериализации (код 40001) и retry-логике.
- Не используй более высокий уровень изоляции, чем нужно: это увеличивает блокировки и снижает конкурентность.
- Для финансовых операций (балансы, переводы) обычно нужен SERIALIZABLE или явные блокировки
SELECT ... FOR UPDATE.
Пример кода
GOpackage mainimport ("context""fmt""log""github.com/jackc/pgx/v5")func main() {ctx := context.Background()conn, err := pgx.Connect(ctx, "postgres://user:pass@localhost/db")if err != nil {log.Fatal(err)}defer conn.Close(ctx)// READ COMMITTED - поведение по умолчаниюtx, err := conn.BeginTx(ctx, pgx.TxOptions{IsoLevel: pgx.ReadCommitted,})if err != nil {log.Fatal(err)}defer tx.Rollback(ctx)var balance interr = tx.QueryRow(ctx, "SELECT balance FROM accounts WHERE id = $1", 1).Scan(&balance)if err != nil {log.Fatal(err)}fmt.Printf("balance: %d\n", balance)// REPEATABLE READ - повторные SELECT вернут тот же snapshottx2, err := conn.BeginTx(ctx, pgx.TxOptions{IsoLevel: pgx.RepeatableRead,})if err != nil {log.Fatal(err)}defer tx2.Rollback(ctx)var count interr = tx2.QueryRow(ctx, "SELECT count(*) FROM orders WHERE total > 100").Scan(&count)if err != nil {log.Fatal(err)}fmt.Printf("orders count: %d\n", count)// SERIALIZABLE - максимальная защита, требует обработки 40001tx3, err := conn.BeginTx(ctx, pgx.TxOptions{IsoLevel: pgx.Serializable,})if err != nil {log.Fatal(err)}defer tx3.Rollback(ctx)// ... операции ...if err := tx3.Commit(ctx); err != nil {// проверяем на ошибку сериализации и повторяем транзакциюlog.Printf("commit failed: %v", err)}}
Как отвечать на собеседовании
Начни с краткого определения каждой аномалии, обязательно укажи разницу между неповторяемым и фантомным чтением - это ключевой момент. Затем приведи таблицу уровней изоляции по стандарту SQL. Упомяни, что PostgreSQL не поддерживает READ UNCOMMITTED в классическом виде и что REPEATABLE READ в PostgreSQL фактически исключает фантомы.
Приведи конкретный пример из практики: например, интернет-магазин, где два менеджера одновременно меняют остатки товара. Покажи, как выбор уровня изоляции влияет на корректность.
Если спросят про Go - скажи, что уровень задаётся через TxOptions в pgx или database/sql, и что драйвер не решает проблему аномалий, он лишь передаёт параметры в СУБД.
Что проверяет интервьюер
- Понимание разницы между тремя аномалиями, особенно между неповторяемым и фантомным чтением.
- Знание стандарта SQL и уровней изоляции.
- Понимание, что уровень изоляции - это trade-off между консистентностью и производительностью.
- Практическое знание: какой уровень выбрать в типовом сценарии.
- Понимание особенностей конкретной СУБД (PostgreSQL в данном случае).
Типичные ошибки
- Путать неповторяемое и фантомное чтение: первое - изменение существующих строк, второе - появление новых.
- Говорить, что READ UNCOMMITTED в PostgreSQL допускает грязное чтение - это не так, PostgreSQL всегда исключает dirty read.
- Утверждать, что REPEATABLE READ по стандарту исключает фантомы - это неверно, стандарт допускает фантомы на этом уровне.
- Не упоминать, что SERIALIZABLE требует обработки ошибок сериализации и retry.
- Считать, что более высокий уровень изоляции всегда лучше - на практике это снижает конкурентность и может привести к дедлокам.
> Похожие задачи по Go
Как обнаружить, что процесс начал занимать больше времени
Как принимается решение, кому звонить при проблемах в нерабочее время
Как должна выглядеть компания, в которую хочешь пойти работать
Чем должна отличаться новая компания от текущей
> Похожие задачи по backend
Как обнаружить, что процесс начал занимать больше времени
Как принимается решение, кому звонить при проблемах в нерабочее время
Как должна выглядеть компания, в которую хочешь пойти работать
Чем должна отличаться новая компания от текущей
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью