47

使用 database/sql 和驱动程序包和 Tx,如果不尝试另一个事务并因此收到错误,然后检查错误以确定事务的类型,就不可能检测到事务是否已提交或回滚。错误。我希望能够从 Tx 对象中确定是否已提交。当然,我可以在使用 Tx 的函数中定义和设置另一个变量,但我有很多变量,每次都是 2 次(变量和赋值)。如果需要,我还有一个延迟函数来执行回滚,并且需要将它传递给 bool 变量。

在 Commit 或 Rollback 之后将 Tx 变量设置为 nil 是否可以接受,并且 GC 是否会恢复任何内存,或者这是一个禁忌,还是有更好的选择?

4

1 回答 1

151

您要确保Begin()Commit()Rollback()出现在同一个函数中。它使交易更易于跟踪,并允许您使用defer.

这是一个示例,它根据是否返回错误执行 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
}

这可能会有点重复。另一种方法是使用事务处理程序包装您的事务:

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 with err = tx.Commit()。我们不这样做,Rollback因为err它是非零的,我们不想覆盖现有的错误。

于 2014-05-06T18:48:48.410 回答