Ответ
Управление транзакциями в Go через стандартный пакет database/sql
включает три ключевых действия: начало транзакции, фиксацию (commit) и откат (rollback).
Основной механизм
db.Begin()
илиdb.BeginTx(ctx, opts)
: Начинает новую транзакцию и возвращает объект*sql.Tx
.tx.Commit()
: Фиксирует все изменения, сделанные в рамках транзакции. Если до этого момента произошла ошибка, вызовCommit()
также вернет ошибку.tx.Rollback()
: Откатывает все изменения, сделанные в транзакции. Этот метод безопасно вызывать несколько раз или послеCommit()
(он просто ничего не сделает).
Пример кода (Best Practice)
Самый надежный способ — использовать defer
с Rollback()
, чтобы гарантировать откат в случае паники или любой ошибки.
func updateUser(db *sql.DB, id int, newName string) error {
// 1. Начинаем транзакцию
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("could not begin transaction: %w", err)
}
// 2. Гарантируем откат, если Commit не будет вызван успешно
// Эта переменная поможет избежать отката уже успешной транзакции
committed := false
defer func() {
if !committed {
tx.Rollback() // Откат, если что-то пошло не так
}
}()
// 3. Выполняем операции внутри транзакции
_, err = tx.Exec("UPDATE users SET name = ? WHERE id = ?", newName, id)
if err != nil {
return fmt.Errorf("could not update user: %w", err)
}
// ... другие операции ...
// 4. Фиксируем транзакцию
if err = tx.Commit(); err != nil {
return fmt.Errorf("could not commit transaction: %w", err)
}
committed = true // Сообщаем defer, что все хорошо
return nil
}
Ключевые моменты:
- Используйте
defer tx.Rollback()
: Это защитит от утечки соединений и незавершенных транзакций при ошибках. - Не игнорируйте ошибки
Commit()
иRollback()
: Ошибка при коммите означает, что изменения не были сохранены. Ошибка при откате может сигнализировать о проблемах с соединением. - Контекст (
context.Context
): Для управления таймаутами и отменой используйтеdb.BeginTx(ctx, nil)
. Если контекст будет отменен, транзакция автоматически откатится. - Оставленная транзакция: Если вы не вызовете ни
Commit()
, ниRollback()
, транзакция останется "висеть" до закрытия соединения. Это плохая практика, так как она удерживает блокировки в БД и занимает соединение из пула.