Ответ
Вложенная транзакция — это транзакция, которая начинается внутри уже существующей (родительской) транзакции. Это позволяет создавать изолированные точки для отката части операций, не затрагивая всю родительскую транзакцию.
Стандартный пакет database/sql в Go не поддерживает вложенные транзакции напрямую, так как он предоставляет общий интерфейс, а не все СУБД имеют такую функциональность.
Эмуляция с помощью точек сохранения (Savepoints)
Наиболее распространенный способ эмулировать вложенные транзакции — использовать точки сохранения (savepoints), которые поддерживаются многими СУБД (например, PostgreSQL, MySQL, SQLite).
SAVEPOINT создает именованную точку внутри транзакции, к которой можно позже откатиться с помощью ROLLBACK TO SAVEPOINT.
Пример реализации
// tx - это уже начатая транзакция *sql.Tx
// Начало "вложенной" транзакции
_, err := tx.Exec("SAVEPOINT my_savepoint")
if err != nil {
log.Fatalf("Failed to create savepoint: %v", err)
}
// Выполняем какие-то операции внутри "вложенной" транзакции
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
// Если произошла ошибка, откатываемся к точке сохранения
log.Printf("Operation failed, rolling back to savepoint: %v", err)
_, rollbackErr := tx.Exec("ROLLBACK TO SAVEPOINT my_savepoint")
if rollbackErr != nil {
log.Fatalf("Failed to rollback to savepoint: %v", rollbackErr)
}
} else {
// Если все успешно, можно "закоммитить" изменения, удалив точку сохранения
// В PostgreSQL это не обязательно, но в некоторых СУБД может быть полезно
_, releaseErr := tx.Exec("RELEASE SAVEPOINT my_savepoint")
if releaseErr != nil {
log.Printf("Failed to release savepoint: %v", releaseErr)
}
}
// В конце родительская транзакция коммитится или откатывается целиком
err = tx.Commit()
if err != nil {
log.Fatalf("Failed to commit transaction: %v", err)
}
Важно
- Некоторые драйверы (например,
pgxдля PostgreSQL) предоставляют более удобные абстракции для работы с точками сохранения. - Синтаксис
SAVEPOINTявляется стандартным для SQL, но всегда лучше сверяться с документацией вашей СУБД.
Ответ 18+ 🔞
Да ты посмотри, что эти умники придумали — вложенные транзакции! Это когда ты внутри одной большой транзакции, типа родительской, начинаешь ещё одну, маленькую. Ну, чтобы если в этой мелкой что-то пошло не так, откатить только её, а не всю конструкцию до основания, блядь.
А вот стандартный пакет database/sql в Go на это смотрит и говорит: «Да похуй, не поддерживаю». Ну а как поддерживать, если не все базы данных это умеют? Он же общий интерфейс, ему со всеми надо уживаться.
Как обычно выкручиваются? Точки сохранения!
Да, вот эта штука — savepoints. Многие базы (PostgreSQL, MySQL) её понимают. Это как зарубка на дереве: поставил — и если накосячил дальше, можно откатиться обратно к этой зарубке, а не валить всё дерево.
// tx - это уже начатая транзакция *sql.Tx
// Ставим зарубку, типа "начало вложенной"
_, err := tx.Exec("SAVEPOINT my_savepoint")
if err != nil {
log.Fatalf("Не удалось поставить зарубку: %v", err)
}
// Пробуем что-то сделать внутри
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
// Ой, всё, пиздец! Катимся назад к зарубке
log.Printf("Всё пошло по пизде, откатываемся к savepoint: %v", err)
_, rollbackErr := tx.Exec("ROLLBACK TO SAVEPOINT my_savepoint")
if rollbackErr != nil {
log.Fatalf("И откатиться-то не смогли, ёпта: %v", rollbackErr)
}
} else {
// Всё чики-пуки, можно зарубку стереть
_, releaseErr := tx.Exec("RELEASE SAVEPOINT my_savepoint")
if releaseErr != nil {
log.Printf("Не удалось стереть зарубку, ну и хуй с ней: %v", releaseErr)
}
}
// А вот тут уже решаем — коммитить всю родительскую транзакцию или нет
err = tx.Commit()
if err != nil {
log.Fatalf("Финальный коммит провалился, пиздец: %v", err)
}
Важный момент, блядь
Некоторые драйверы, тот же pgx для PostgreSQL, могут дать тебе готовые обёртки, чтобы не писать эти SAVEPOINT руками. Но в целом идея одна — ставишь зарубку, работаешь, а если наебнулся, откатываешься к ней. Главное, в документацию своей СУБД глянь, а то вдруг она эту хуйню не понимает!