2013-04-24 98 views
21

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

在提交或回滚之后将Tx变量设置为零并且GC是否能够恢复任何内存,或者是否否,还是有更好的选择吗?数据库/ sql发送 - 检测提交或回滚

+1

不知道如果我理解这个问题。你必须用Commit或Rollback结束一个事务,这样你就知道你做了什么,但是你不想在一个额外的变量中记住这一点?你可以将Tx和bool包装在自己的RememberingTx中,这会减少行数。关于GC问题:无论你是否设置为零,无关紧要:一旦没有引用它,内存将被回收。所以:是的,你可以有'var tx * Tx;剪断;如果cond {tx.Commit; tx = nil} else {tx.Rollback};剪断;如果tx == nil {被提交} else {被回滚}'但它感觉很难看。 – Volker 2013-04-24 12:28:18

+0

这就是它的意义,但是如果Tx不为零,则会有一个延迟函数进行回滚。一旦交易提交,交易无法使用,所以我打算将它设置为零。这不是很好,但是尝试回滚并测试错误消息也不是很好。问题是AFAIK没有办法测试事务是否从Tx“完成”。我不知道为什么这样做,也许是表现。 – 2013-04-24 18:01:25

回答

73

为什么你甚至需要这样做?调用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,看我是否应该提交或回滚。然而,上面的例子并没有处理恐慌。

我不喜欢在每个数据库例程上执行提交/回滚逻辑,所以我通常将它们包装在一个事务处理程序中。沿此线的东西:

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()) 
} 

http://play.golang.org/p/0OinYDWFlx

+0

不错的答案!我认为你错过了第二个doSomething()实现结束时的“return nil”。 – splinter123 2015-09-05 15:27:21

+0

卢克,如何以及何时得到评估?根据文档,“err”应该在延迟调用中首次声明时获得它的值。所以对我来说这实际上有点令人困惑,因为推迟使用的“err”的值似乎会改变。 – mirage 2016-10-19 04:11:23

+0

err是通过以下方式声明的:=(冒号等于)。 anon func捕获它。在返回值之前调用延迟。这允许它被设置。当发生恐慌时,它会被恢复,变成错误,然后返回。如果以任何方式发生错误,则会发生回滚。最后,如果没有错误,并且err(当前为零)被设置为Commits返回值以防万一发生错误,则会发生提交。 – Luke 2016-10-20 08:14:21