Ответ
В Go управление транзакциями осуществляется через объект *sql.Tx
, который создается вызовом db.BeginTx()
. Для тонкой настройки поведения транзакции, включая уровень изоляции и режим "только чтение", используется структура sql.TxOptions
.
Уровни изоляции транзакций (Isolation Levels)
Уровень изоляции определяет, насколько одна транзакция защищена от изменений, вносимых другими параллельными транзакциями. Это компромисс между согласованностью данных и производительностью.
В пакете database/sql
определены следующие константы:
sql.LevelDefault
: Используется уровень по умолчанию, заданный в самой базе данных.sql.LevelReadUncommitted
: Самый низкий уровень. Позволяет читать незафиксированные изменения ("грязное чтение").sql.LevelReadCommitted
: Предотвращает "грязное чтение". Транзакция видит только те данные, которые были зафиксированы до ее начала. Это стандартный выбор для многих систем.sql.LevelRepeatableRead
: Гарантирует, что при повторном чтении в рамках одной транзакции данные будут теми же. Однако возможны "фантомные чтения" (появление новых строк).sql.LevelSerializable
: Самый строгий уровень. Полностью изолирует транзакции друг от друга, предотвращая все аномалии, включая фантомные чтения. Гарантирует максимальную согласованность, но может сильно снизить производительность.
Режим «только чтение» (ReadOnly
)
Поле ReadOnly: true
в sql.TxOptions
— это подсказка для драйвера базы данных, что транзакция не будет изменять данные. Некоторые СУБД могут использовать это для оптимизации производительности. Не все драйверы поддерживают эту опцию.
Пример использования
import (
"context"
"database/sql"
"log"
)
func performTransaction(ctx context.Context, db *sql.DB) error {
// Задаем опции: уровень изоляции Serializable и режим "только чтение"
opts := &sql.TxOptions{
Isolation: sql.LevelSerializable,
ReadOnly: true, // Устанавливаем в false для операций записи
}
// Начинаем транзакцию с заданными опциями
tx, err := db.BeginTx(ctx, opts)
if err != nil {
return err
}
// defer tx.Rollback() — это страховка.
// Если Commit() будет выполнен успешно, Rollback() вернет ошибку sql.ErrTxDone,
// которую можно безопасно игнорировать.
defer tx.Rollback()
var balance int
// Выполняем чтение внутри транзакции
err = tx.QueryRowContext(ctx, "SELECT balance FROM accounts WHERE id = 1").Scan(&balance)
if err != nil {
return err // Rollback будет вызван через defer
}
log.Printf("Current balance: %d", balance)
// Если бы мы изменяли данные:
// _, err = tx.ExecContext(ctx, "UPDATE ...")
// if err != nil { return err }
// Фиксируем транзакцию
return tx.Commit()
}
Как выбрать?
- Простые CRUD-операции:
LevelReadCommitted
обычно является хорошим балансом. - Отчеты или длительные операции чтения:
LevelRepeatableRead
илиReadOnly: true
для консистентности данных. - Критические финансовые или складские операции:
LevelSerializable
для предотвращения любых аномалий, даже ценой производительности.