数据库/sql Tx - 检测提交或回滚
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16184238/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
database/sql Tx - detecting Commit or Rollback
提问by Brian Oh
Using the database/sql and driver packages and Tx, it is not possible it appears to detect whether a transaction has been committed or rolled-back without attempting another and receiving an error as a result, and then examining the error to determine the type of error. I would like to be able to determine from the Tx object whether committed or not. Sure, I can define and set another variable in the function that uses Tx, but I have quite a number of them, and it is times 2 every time (variable and assignment). I also have a deferred function to do a Rollback if needed, and it needs to be passed the bool variable.
Would it be acceptable to set the Tx variable to nil after a Commit or Rollback, and will the GC recover any memory, or is that a no-no, or is there a better alternative?
使用数据库/sql 和驱动程序包和 Tx,似乎不可能检测一个事务是否已提交或回滚而不尝试另一个事务并因此收到错误,然后检查错误以确定类型错误。我希望能够从 Tx 对象确定是否已提交。当然,我可以在使用 Tx 的函数中定义和设置另一个变量,但我有相当多的变量,每次都是 2 次(变量和赋值)。如果需要,我还有一个延迟函数来执行回滚,并且需要传递 bool 变量。
在提交或回滚之后将 Tx 变量设置为 nil 是否可以接受,并且 GC 是否会恢复任何内存,或者这是一个禁忌,还是有更好的选择?
回答by Luke
You want to make sure that Begin()
, Commit()
, and Rollback()
appear within the same function. It makes transactions easier to track, and lets you ensure they are closed properly by using a defer
.
你要确保Begin()
,Commit()
以及Rollback()
同样的功能内出现。它使交易更容易跟踪,并让您确保它们通过使用defer
.
Here is an example of this, which does a Commit or Rollback depending on whether an error is returned:
这是一个示例,它根据是否返回错误执行 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
}
This can get a bit repetitive. Another way of doing this is by wrapping your transactions using a transaction handler:
这可能会有点重复。另一种方法是使用事务处理程序包装您的事务:
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
}
Using the transaction hander above, I can do this:
使用上面的事务处理程序,我可以这样做:
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
})
}
This keeps my transactions succinct and ensures by transactions are properly handled.
这使我的交易保持简洁并确保交易得到正确处理。
In my transaction handler I use recover()
to catch panics to ensure a Rollback happens right away. I re-throw the panic to allow my code to catch it if a panic is expected. Under normal circumstances a panic should not occur. Errors should be returned instead.
在我的事务处理程序中,我recover()
用来捕捉恐慌以确保立即发生回滚。如果预期会出现恐慌,我会重新抛出恐慌以允许我的代码捕获它。在正常情况下,不应发生恐慌。应该返回错误。
If we did not handle panics the transaction would be rolled back eventually. A non-commited transaction gets rolled back by the database when the client disconnects or when the transaction gets garbage collected. However, waiting for the transaction to resolve on its own could cause other (undefined) issues. So it's better to resolve it as quickly as possible.
如果我们不处理恐慌,事务最终将被回滚。当客户端断开连接或事务被垃圾收集时,数据库会回滚未提交的事务。但是,等待事务自行解决可能会导致其他(未定义的)问题。所以最好尽快解决。
One thing that may not be immediately clear is that defer
can change the return value within a closure if the return variable is captured. In the transaction handler the transaction is committed when err
(the return value) is nil. The call to Commit
can also return an error, so we set its return to err with err = tx.Commit()
. We do not do the same with Rollback
because err
is non-nil and we do not want to overwrite the existing error.
可能无法立即清楚的一件事是,defer
如果返回变量被捕获,则可以更改闭包中的返回值。在事务处理程序中,当err
(返回值)为零时提交事务。对 的调用Commit
也可能返回错误,因此我们将其返回设置为 err err = tx.Commit()
。我们不这样做,Rollback
因为err
is non-nil 并且我们不想覆盖现有的错误。