> Что такое грязное чтение, фантомное чтение и неповторяемое чтение в транзакциях (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.

Пример кода

GO
package main
import (
"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 int
err = 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 вернут тот же snapshot
tx2, err := conn.BeginTx(ctx, pgx.TxOptions{
IsoLevel: pgx.RepeatableRead,
})
if err != nil {
log.Fatal(err)
}
defer tx2.Rollback(ctx)
var count int
err = 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 - максимальная защита, требует обработки 40001
tx3, 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.
  • Считать, что более высокий уровень изоляции всегда лучше - на практике это снижает конкурентность и может привести к дедлокам.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью