Используя пакеты базы данных /sql и драйверов и Tx, невозможно обнаружить, была ли транзакция зафиксирована или откатна без попытки получения другой и получения ошибки в результате, а затем изучения ошибки для определения тип ошибки. Я хотел бы иметь возможность определить из объекта Tx, независимо от того, было оно совершено или нет. Конечно, я могу определить и установить другую переменную в функции, которая использует Tx, но у меня их довольно много, и каждый раз это время 2 (переменная и назначение). У меня также есть отложенная функция для выполнения отката, если это необходимо, и ему необходимо передать переменную bool.
Было бы приемлемо установить Tx-переменную в nil после Commit или Rollback, и GC восстановит любую память, или это нет-нет, или есть лучшая альтернатива?
База данных /sql Tx - обнаружение фиксации или откат
Ответ 1
Зачем вам это нужно? Функция, вызывающая Begin()
, должна также вызывать Commit()
или Rollback()
и возвращать соответствующую ошибку.
Например, этот код выполняет фиксацию или откат в зависимости от того, возвращается ли ошибка:
func (s Service) DoSomething() (err error) {
tx, err := s.db.Begin()
if err != nil {
return
}
defer func() {
if err != nil {
tx.Rollback()
return
}
err = tx.Commit()
}()
if _, err = tx.Exec(...); err != nil {
return
}
if _, err = tx.Exec(...); err != nil {
return
}
// ...
return
}
Обратите внимание, как я проверяю error
, чтобы узнать, следует ли мне совершать или откатывать. Однако приведенный выше пример не обрабатывает паники.
Мне не нравится делать логику commit/rollback для каждой процедуры базы данных, поэтому я обычно обертываю их в обработчик транзакций. Что-то вроде этого:
func Transact(db *sql.DB, txFunc func(*sql.Tx) error) (err error) {
tx, err := db.Begin()
if err != nil {
return
}
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p) // re-throw panic after Rollback
} else if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()
err = txFunc(tx)
return err
}
Это позволяет мне сделать это вместо:
func (s Service) DoSomething() error {
return Transact(s.db, func (tx *sql.Tx) error {
if _, err := tx.Exec(...); err != nil {
return err
}
if _, err := tx.Exec(...); err != nil {
return err
}
})
}
Обратите внимание, что если что-либо внутри моей паники транзакций автоматически обрабатывается обработчиком транзакций.
В моей реальной реализации я передаю интерфейс вместо * sql.Tx, чтобы предотвратить нежелательные вызовы Commit()
или Rollback()
.
Вот простой фрагмент, демонстрирующий, как работает defer
(печатает 4, а не 5):
package main
func test() (i int) {
defer func() {
i = 4
}()
return 5
}
func main() {
println(test())
}