Что такое уровни изоляции транзакций в базах данных и как с ними работать в Go?

Ответ

Уровни изоляции транзакций — это настройка в СУБД, которая определяет, насколько одна транзакция "видит" изменения, сделанные в других параллельных транзакциях. Это компромисс между согласованностью данных и производительностью.

Стандарт SQL определяет 4 основных уровня, каждый из которых решает определенные проблемы конкурентного доступа:

  1. Read Uncommitted: Самый слабый уровень. Позволяет читать незафиксированные ("грязные") данные. Используется редко из-за непредсказуемых результатов.
  2. Read Committed: Предотвращает "грязное чтение". Транзакция видит только те данные, которые были зафиксированы (committed) до её начала. Это уровень по умолчанию для многих СУБД (например, PostgreSQL, Oracle).
  3. Repeatable Read: Предотвращает "грязное" и "неповторяемое чтение" (non-repeatable read). Если транзакция читает одну и ту же строку несколько раз, она получит один и тот же результат. Уровень по умолчанию в MySQL.
  4. Serializable: Самый строгий уровень. Гарантирует полную изоляцию транзакций, как если бы они выполнялись последовательно. Предотвращает все аномалии, включая "фантомное чтение" (phantom reads), но может снижать производительность и приводить к ошибкам сериализации.

Как работать в Go:

В Go уровень изоляции можно указать при создании транзакции с помощью sql.TxOptions. Пакет database/sql предоставляет константы для стандартных уровней.

import (
    "context"
    "database/sql"
    "log"
)

func performTransaction(db *sql.DB) {
    ctx := context.Background()

    // Начинаем транзакцию с уровнем изоляции Repeatable Read
    tx, err := db.BeginTx(ctx, &sql.TxOptions{
        Isolation: sql.LevelRepeatableRead,
    })
    if err != nil {
        log.Fatal(err)
    }

    // Выполняем операции внутри транзакции
    _, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - 100 WHERE id = 1")
    if err != nil {
        // Если произошла ошибка, откатываем транзакцию
        tx.Rollback()
        log.Fatal(err)
    }

    // Если все успешно, фиксируем транзакцию
    if err := tx.Commit(); err != nil {
        log.Fatal(err)
    }
}