Что такое уровень изоляции Repeatable Read и от каких аномалий он защищает?

Ответ

Repeatable Read — это один из стандартных уровней изоляции транзакций в базах данных. Он гарантирует, что если в рамках одной транзакции несколько раз прочитать одни и те же строки, результат будет одинаковым.

По сути, транзакция работает со «снимком» данных, сделанным на момент её начала. Изменения, закоммиченные другими транзакциями после старта текущей, не будут видны.

От каких аномалий защищает:

  1. Dirty Read (Грязное чтение): Чтение данных, которые были изменены другой транзакцией, но еще не закоммичены. Repeatable Read полностью предотвращает это.
  2. Non-Repeatable Read (Неповторяющееся чтение): Ситуация, когда при повторном чтении в одной транзакции одна и та же строка возвращает разные данные (т.к. другая транзакция успела их изменить и закоммитить). Repeatable Read решает эту проблему.

Какую проблему НЕ решает:

  • Phantom Read (Фантомное чтение): Ситуация, когда при повторном чтении диапазона строк в одной транзакции обнаруживаются новые строки, которые были добавлены и закоммичены другой транзакцией. Например, SELECT COUNT(*) FROM users WHERE age > 30 может вернуть разные результаты.

Пример на Go (с database/sql и PostgreSQL):

// Начинаем транзакцию с нужным уровнем изоляции
tx, err := db.BeginTx(ctx, &sql.TxOptions{
    Isolation: sql.LevelRepeatableRead,
})
if err != nil {
    log.Fatal(err)
}
// defer tx.Rollback() — хорошая практика для отката в случае ошибок
defer tx.Rollback()

// Первое чтение
var balance int
tx.QueryRow("SELECT balance FROM accounts WHERE id = 1").Scan(&balance)
fmt.Println("First read:", balance) // Например, 1000

// Представим, что в этот момент другая транзакция изменила баланс на 500 и закоммитила изменения

// Повторное чтение в НАШЕЙ транзакции
var newBalance int
tx.QueryRow("SELECT balance FROM accounts WHERE id = 1").Scan(&newBalance)
fmt.Println("Second read:", newBalance) // Все равно будет 1000

// Завершаем транзакцию
err = tx.Commit()

Этот уровень является хорошим компромиссом между производительностью и консистентностью, сильнее чем Read Committed, но слабее Serializable.