Ответ
Для работы с транзакциями в Go используется объект *sql.Tx, который создается из *sql.DB.
Основной флоу работы с транзакцией:
- Начать транзакцию с помощью
db.Begin()илиdb.BeginTx(). - Выполнять все запросы (SELECT, INSERT, UPDATE, DELETE) через объект транзакции
tx, а не черезdb. - Если любая из операций завершилась ошибкой, откатить транзакцию с помощью
tx.Rollback(). - Если все операции успешны, зафиксировать изменения с помощью
tx.Commit().
Пример с использованием defer для надежности:
Этот паттерн гарантирует, что транзакция будет отменена, если что-то пойдет не так.
db, err := sql.Open("postgres", "user=... password=... dbname=... sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 1. Начинаем транзакцию
ctx := context.Background()
tx, err := db.BeginTx(ctx, nil) // Используем BeginTx для передачи контекста
if err != nil {
log.Fatal(err)
}
// 2. Откладываем Rollback. Если Commit выполнится успешно,
// Rollback вернет ошибку sql.ErrTxDone, которую можно безопасно игнорировать.
defer tx.Rollback()
// 3. Выполняем операции внутри транзакции
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
// Ошибка уже обработается в defer tx.Rollback()
log.Printf("Failed to debit account: %v", err)
return
}
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance + 100 WHERE id = 2")
if err != nil {
log.Printf("Failed to credit account: %v", err)
return
}
// 4. Если все хорошо, коммитим транзакцию
if err = tx.Commit(); err != nil {
log.Fatal(err)
}
fmt.Println("Transaction committed successfully")Важные моменты:
- Изоляция: Для установки уровня изоляции транзакции (например,
sql.LevelSerializable) используйтеdb.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable}). - Контекст: Передавайте
context.ContextвBeginTxиExecContext/QueryRowContextдля управления временем выполнения и отменой запросов. - Всегда используйте
tx: Послеdb.Begin(), все операции должны выполняться через объектtx, чтобы они были частью одной транзакции.