Ответ
Нет, стандартный пакет 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 усложняет код и зависит от конкретной СУБД, поэтому часто лучшим решением является пересмотр логики для избежания необходимости во вложенных транзакциях.
Ответ 18+ 🔞
Ага, слушай, вот сидишь ты такой, пишешь на Go, думаешь: "Ща сделаю транзакцию внутри транзакции, будет красиво, как в сказке про матрёшку". А потом — бац! — и оказывается, что стандартный пакет database/sql про эту твою матрёшку нихуя не знает. Прям как Герасим из того рассказа, только вместо "Муму" он тебе выдаст ошибку, если у *sql.Tx метод Begin() вызвать. Вообще, блядь, не существует такого метода, он только у *sql.DB водится. Ёперный театр!
Но не всё так грустно, как кажется. Некоторые базы данных, эти хитрожопые системы, вроде PostgreSQL или MySQL, поддерживают штуку под названием 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. Всё откатится, как будто ничего и не было. Пиздец, да?
И ещё: код становится сложнее, и для каждой СУБД свои пляски. Поэтому, прежде чем городить эту хуйню с точками сохранения, подумай — а может, проще переделать логику, чтобы обойтись без этих вложенных транзакций? Часто так и есть. А то получится, как в том анекдоте: "зачем просто, если можно сложно, с матом и точками сохранения".