Допустимо ли в Go создавать вложенные транзакции с помощью пакета `database/sql`?

Ответ

Нет, стандартный пакет database/sql в Go не поддерживает концепцию вложенных транзакций напрямую. Если вы находитесь внутри объекта *sql.Tx и вызовете у него метод Begin(), вы получите ошибку, так как этот метод существует только у *sql.DB.

Однако, некоторые СУБД (например, PostgreSQL, MySQL, MS SQL Server) поддерживают механизм SAVEPOINT (точек сохранения), который позволяет эмулировать поведение вложенных транзакций.

Работать с ними нужно вручную, выполняя SQL-команды:

  • SAVEPOINT <имя_точки>: создает точку сохранения.
  • ROLLBACK TO SAVEPOINT <имя_точки>: откатывает транзакцию до указанной точки.
  • RELEASE SAVEPOINT <имя_точки>: удаляет точку сохранения, оставляя изменения.

Пример эмуляции для PostgreSQL:

tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}
// Гарантируем откат в случае паники или раннего выхода
defer tx.Rollback()

// Основная часть транзакции
_, err = tx.Exec(`INSERT INTO users (name) VALUES ('Alice')`)
if err != nil { ... }

// Эмуляция "вложенной" транзакции
_, err = tx.Exec("SAVEPOINT sp1")
if err != nil { ... }

_, err = tx.Exec(`UPDATE users SET balance = balance - 100 WHERE name = 'Alice'`)
if err != nil {
    // "Откатываем" вложенную транзакцию
    tx.Exec("ROLLBACK TO SAVEPOINT sp1")
} else {
    // "Коммитим" вложенную транзакцию
    tx.Exec("RELEASE SAVEPOINT sp1")
}

// Коммитим основную транзакцию
if err = tx.Commit(); err != nil {
    log.Fatal(err)
}

Важно: Это не настоящие вложенные транзакции. Если вы откатите внешнюю транзакцию (tx.Rollback()), все изменения, включая те, что были сделаны внутри SAVEPOINT, будут отменены. Использование SAVEPOINT усложняет код и зависит от конкретной СУБД, поэтому часто лучшим решением является пересмотр логики для избежания необходимости во вложенных транзакциях.