背景

我在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失败到我的保存点。

    可能的解决方案的问题

    我不确定使用保存点是否会干扰数据库/sql包的行为。另外,我的解决方案似乎有些困惑-当然,有一种更干净的方法可以做到这一点!

    最佳答案

    我也遇到了类似的问题:一笔交易中有很多步骤。
    开始交易后:

  • BEGIN
  • 循环中:
  • SAVEPOINT s1
  • 一些 Action ....
  • 如果出现错误:ROLLBACK TO SAVEPOINT s1
  • 如果可以,请Go 下一步
  • 最终COMMIT

  • 这种方法使我能够一步一步执行所有步骤。如果某些步骤失败了,我只能丢掉它们,保留其他步骤。最后落实所有“良好”的工作。

    10-02 10:48