为什么要推迟回滚操作?

22

我已开始在 web 服务中使用 Go,并且有一些数据库交互(惊喜!!!)我发现了这个例子:

tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}
defer tx.Rollback()
stmt, err := tx.Prepare("INSERT INTO foo VALUES (?)")
if err != nil {
    log.Fatal(err)
}
defer stmt.Close() // danger!
for i := 0; i < 10; i++ {
    _, err = stmt.Exec(i)
    if err != nil {
        log.Fatal(err)
    }
}
err = tx.Commit()
if err != nil {
    log.Fatal(err)
}
// stmt.Close() runs here!

来自http://go-database-sql.org/prepared.html

这个例子写得很好,易于理解。然而,它让我有一个未解答的问题。为什么要defer事务Rollback调用?

为什么不直接这样做:

err := tx.Commit()

if err != nil {
    log.Error(err)
    tx.Rollback()
}
< p>如果tx.Commit()成功,那么defer tx.Rollback()不会尝试回滚吗?或者我对defer的理解有误?


3
看起来有一个错误。正如你所说,除非出现错误,否则你不想回滚。另外,log.Fatalf在实际应用程序中是不必要的。我建议使用“改进此页面”按钮并联系作者。 - Kenny Grant
1
谢谢你的回答 :) - Lars Nielsen
@KennyGrant 点赞,因为你改进了这个误导性的例子。 - aristotll
3
使用 defer tx.Rollback() 可以很好地避免在事务生命周期中的每个错误都放置回滚逻辑。请注意,如果事务已经提交,则调用回滚将执行“NOP”。话虽如此,随着冗余性的增加,控制力也会增加,因此如果速度是一个问题,则处理每个回滚而不是使用 defer 可能更可取(或用于详细记录日志等)。 - Kelly Flet
2个回答

44

重要的是,如果您延迟 tx.Rollback(),那么即使您提前返回,也可以确保它将被调用,“诀窍”是在已提交的事务上调用tx.Rollback()实际上不会回滚,因为一旦事务提交,就已经提交了,并且没有办法回滚它:)因此,这是一个简化代码的巧妙技巧。


3
在成功提交后进行额外的回滚操作,是否会导致与服务器的往返通信,即使这实际上是一个无操作(NOP)?如果是这种情况,并且有连续100个此类(成功)操作,那么这个“技巧”将导致性能下降。 - bosvos
6
е—Ё @bosvosпәЊдёҚдәљзљ„гЂ‚дҢ еЏҮд»ӨжџӨзњ‹database/sql/sql.goдё­зљ„Rollback()д»Ә码。它首先执иҰЊatomic.CompareAndSwapInt32(&tx.done, 0, 1)пәЊе¦‚жһњдғ‹еЉҰе·Іе®Њж€ђпәЊе€™з«‹еҚіиү”е›һгЂ‚ - Tomor

8

这个例子有点误导人。它使用log.Fatal(err)来处理错误。通常情况下,你不会这样做,而是使用return err。因此,延迟回滚(deferred rollback)的目的在于确保在提前返回的情况下回滚事务。


是的,我知道关于log.Fatal部分的 :) 非常感谢你的回答。 - Lars Nielsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接