我正在使用带有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
})
}
目前代码无法正常工作,因为我提交了两次交易(一次在runInTransaction
,一次在SampleFunc
)。
在我提交交易的地方,我可以改为运行tx.Exec("SAVEPOINT my_savepoint")
,然后defer tx.Exec("ROLLBACK TO SAVEPOINT my_savepoint")
在B点的代码之后,我可以运行:tx.Exec("RELEASE SAVEPOINT my_savepoint")
因此,如果B点的代码运行没有错误,我将无法ROLLBACK
到我的保存点。
我不确定使用保存点是否会破坏数据库/ sql包的行为。此外,我的解决方案似乎有点凌乱 - 当然有更简洁的方法来做到这一点!
答案 0 :(得分:2)
您可以在两个交易中分割您的工作:
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
})
}
答案 1 :(得分:1)
我有同样的问题:我在一次交易中有很多步骤。 开始交易后:
BEGIN
循环:
SAVEPOINT s1
ROLLBACK TO SAVEPOINT s1
最后COMMIT
这种方法使我能够逐个执行所有步骤。如果某些步骤失败,我可以只丢弃他们,保留其他人。并最终承诺所有"好"工作