使用数据库/ sql和驱动程序包以及Tx,它似乎无法检测事务是否已提交或回滚而不尝试另一个事务并因此接收错误,然后检查错误以确定错误的类型。我希望能够从Tx对象确定是否已提交。当然,我可以在使用Tx的函数中定义和设置另一个变量,但我有很多它们,每次都是2次(变量和赋值)。如果需要,我还有一个延迟函数来执行回滚,它需要传递bool变量。
在提交或回滚之后将Tx变量设置为nil是否可以接受,并且GC是否会恢复任何内存,或者是否为no-no,还是有更好的替代方案?
答案 0 :(得分:107)
您希望确保Begin()
,Commit()
和Rollback()
出现在同一个功能中。它使事务更容易跟踪,并允许您使用defer
确保它们正确关闭。
以下是此示例,它根据是否返回错误执行提交或回滚:
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
}
这可能会有点重复。另一种方法是使用事务处理程序包装事务:
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() // err is non-nil; don't change it
} else {
err = tx.Commit() // err is nil; if Commit returns error update err
}
}()
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
}
return nil
})
}
这使我的交易简洁明了,并确保妥善处理交易。
在我的事务处理程序中,我使用recover()
来捕获恐慌,以确保立即发生回滚。如果恐慌预期,我会重新抛出恐慌,让我的代码能够抓住它。在正常情况下,不应发生恐慌。应该返回错误。
如果我们没有处理恐慌,最终会回滚交易。当客户端断开连接或事务被垃圾收集时,数据库将回滚非提交的事务。但是,等待事务自行解决可能会导致其他(未定义)问题。所以最好尽快解决它。
有些事情可能不会立即明确,如果捕获了返回变量,defer
可以更改闭包内的返回值。在事务处理程序中,当err
(返回值)为nil时,将提交事务。对Commit
的调用也会返回错误,因此我们将其返回错误设置为err = tx.Commit()
。我们不会对Rollback
执行相同操作,因为err
为非零,我们不想覆盖现有错误。