背景
我正在使用带有 Postgres 数据库的 github.com/jmoiron/sqlx
golang 包。
我有以下包装函数来在事务中运行 SQL 代码:
func (s *postgresStore) runInTransaction(ctx context.Context, fn func(*sqlx.Tx) error) error {
tx, err := s.db.Beginx()
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
return
}
err = tx.Commit()
}()
err = fn(tx)
return err
}
鉴于此,请考虑以下代码:
func (s *store) SampleFunc(ctx context.Context) error {
err := s.runInTransaction(ctx,func(tx *sqlx.Tx) error {
// Point A: Do some database work
if err := tx.Commit(); err != nil {
return err
}
// Point B: Do some more database work, which may return an error
})
}
期望的行为
- 如果在 A 点有错误,那么交易应该完成零工作
- 如果B点有错误,那么事务应该还是完成了A点的工作。
当前代码有问题
代码目前没有按预期工作,因为我提交了两次事务(一次在 runInTransaction
中,一次在 SampleFunc
中)。
一个可能的解决方案
在我提交交易的地方,我可以运行类似tx.Exec("SAVEPOINT my_savepoint")
的东西,然后defer tx.Exec("ROLLBACK TO SAVEPOINT my_savepoint")
在 B 点的代码之后,我可以运行:tx.Exec("RELEASE SAVEPOINT my_savepoint")
因此,如果 B 点的代码运行没有错误,我将无法ROLLBACK
到我的保存点。
可能解决的问题
我不确定使用保存点是否会扰乱 database/sql 包的行为。此外,我的解决方案似乎有点困惑——肯定有更简洁的方法来做到这一点!
最佳答案
多笔交易
您可以将您的工作分成两个事务:
func (s *store) SampleFunc(ctx context.Context) error {
err := s.runInTransaction(ctx,func(tx *sqlx.Tx) error {
// Point A: Do some database work
})
if err != nil {
return err
}
return s.runInTransaction(ctx,func(tx *sqlx.Tx) error {
// Point B: Do some more database work, which may return an error
})
}
关于sql - 有什么简单的方法可以在事务进行到一半时提交工作,然后继续,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44858531/