【问题标题】:database/sql Tx - detecting Commit or Rollbackdatabase/sql Tx - 检测提交或回滚
【发布时间】:2013-04-24 05:45:26
【问题描述】:

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

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

【问题讨论】:

  • 不确定我是否理解问题所在。您必须使用 Commit 或 Rollback 结束事务,这样您才能知道自己做了什么,但又不想在额外的变量中记住这一点?您可以将 Tx 和 bool 包装在自己的 RememberingTx 中,这会稍微减少行数。关于 GC 问题:设置为 nil 与否都没有关系:一旦没有对它的引用,内存将被回收。所以:是的,你可以拥有var tx *Tx; snip; if cond { tx.Commit; tx=nil } else { tx.Rollback}; snip; if tx==nil { was commited } else { was rollbacked},但感觉很难看。
  • 就是这样,但是如果 Tx 不为零,则有一个延迟函数执行回滚。一旦提交了事务,Tx 无论如何都不能使用,所以我打算将它设置为 nil。它并不漂亮,但是尝试回滚并测试错误消息也不漂亮。问题是AFAIK无法测试交易是否从Tx“完成”。我不确定为什么要这样做,也许是性能。

标签: sql go


【解决方案1】:

您要确保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(返回值)为零时,事务被提交。对Commit 的调用也可能返回错误,因此我们将其返回设置为使用err = tx.Commit() 的err。我们不会对 Rollback 做同样的事情,因为 err 不是 nil,我们不想覆盖现有的错误。

【讨论】:

  • 不错的答案!我认为您在第二次 doSomething() 实施结束时错过了“返回零”。
  • Luke,err 如何以及何时被评估?根据文档,“err”应该在第一次在 defer 调用中声明时获得它的值。所以这对我来说实际上有点令人困惑,因为在 defer 中使用的“err”的值似乎发生了变化。
  • err 在 defer 之前通过 := (冒号等号)声明。 anon 函数捕获它。在返回值之前调用 Defer。这允许它被设置。当发生恐慌时,它会被恢复,变成错误,然后返回。如果以任何方式发生错误,则会发生回滚。最后,如果没有错误并且 err(当前为 nil)设置为 Commits 返回值以防出错,则会发生提交。
  • @ChadGilbert 虽然最近的编辑让读者更清楚地知道发生了什么,但只是想让您知道它以前有效。 return 设置 err 变量。见play.golang.org/p/66wWTJl0pH
  • @Ratatouille Rollback 和 Commit 语句不应该恐慌。 recover() 语句旨在捕获由事务中的代码引起的恐慌。理想情况下,你永远不会惊慌——你会返回一个错误,这也会导致回滚。如果没有调用 Rollback 或 Commit 并且事务从未解决(发生断开连接;或者事务被垃圾收集),则数据库将执行回滚。如果有一个错误,则提交返回一个错误(您可以在返回完成之前在 defer 内更改err)。回滚不会覆盖导致它的现有错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多