【问题标题】:pg-go RunInTransaction not rolling back the transactionpg-go RunInTransaction 不回滚事务
【发布时间】:2021-02-03 13:49:54
【问题描述】:

我正在尝试在我的单元测试中回滚一个事务,在不同场景之间,以保持数据库为空并且不使我的测试变脏。所以,我正在尝试:

for _, test := range tests {
   db := connect()
   _ = db.RunInTransaction(func() error {
      t.Run(test.name, func(t *testing.T) {
         for _, r := range test.objToAdd {
            err := db.PutObj(&r)
            require.NoError(t, err)
         }
         objReturned, err := db.GetObjsWithFieldEqualsXPTO()
         require.NoError(t, err)
         require.Equal(t, test.queryResultSize, len(objReturned))
      })
      return fmt.Errorf("returning error to clean up the database rolling back the transaction")
   })
}

我希望在场景结束时回滚事务,因此下一步将有一个空数据库,但是当我运行时,数据从未回滚。

我相信我正在尝试按照文档的建议进行操作:https://pg.uptrace.dev/faq/#how-to-test-mock-database,对吗?

更多信息:我注意到我的接口在 RunInTransaction 上实现了一个层:

func (gs *DB) RunInTransaction(fn func() error) error {
    f := func(*pg.Tx) error { return fn() }
    return gs.pgDB.RunInTransaction(f)
}

IDK 有什么问题,但我真的猜想这与此有关(因为 TX 被封装在 RunInTransaction 实现中。

【问题讨论】:

  • 与您的问题没有直接关系,但您可能会发现this helpful--它基本上为您完成所有交易。
  • 作为我的第一次尝试,我会检查 db.RunInTransaction 的返回值,也许回滚在某处失败。
  • 调试,代码按预期执行,回滚行通过,错误返回(即使我添加了一个require.Error来确认,它通过了....但即便如此,数据库仍在继续在每个场景之后提交值。
  • 我注意到我的 runInTransaction 已实现并封装了 TX。如果有帮助,请将代码添加到问题中。

标签: postgresql go testify


【解决方案1】:

go-pg 使用连接池(与大多数 go 数据库包一样)。这意味着当你调用一个数据库函数(例如db.Exec)时,它会从池中获取一个连接(如果需要,建立一个新的),运行命令并将连接返回到池中。

运行事务时,您需要在专用于事务的单个连接上运行BEGIN,无论您需要什么更新等,然后是COMMIT/ROLLBACK(在其他连接上发送的任何命令都不属于交易)。这就是为什么Begin()(实际上是RunInTransaction)为您提供pg.Tx;使用它在事务中运行命令。

example_test.go 提供了一个涵盖RunInTransaction 用法的示例:

incrInTx := func(db *pg.DB) error {
        // Transaction is automatically rollbacked on error.
        return db.RunInTransaction(func(tx *pg.Tx) error {
            var counter int
            _, err := tx.QueryOne(
                pg.Scan(&counter), `SELECT counter FROM tx_test FOR UPDATE`)
            if err != nil {
                return err
            }

            counter++

            _, err = tx.Exec(`UPDATE tx_test SET counter = ?`, counter)
            return err
        })
    }

您会注意到,这仅在调用RunInTransaction 时使用pg.DB;所有数据库操作都使用事务txpg.Tx)。 tx.QueryOne 将在事务中运行;如果您运行db.QueryOne,那么它将在事务之外运行。

所以RunInTransaction 开始一个事务并将相关的Tx 作为参数传递给您提供的函数。你把它包装起来:

func (gs *DB) RunInTransaction(fn func() error) error {
    f := func(*pg.Tx) error { return fn() }
    return gs.pgDB.RunInTransaction(f)
}

这实际上忽略了pg.Tx,然后您使用其他连接(例如err := db.PutObj(&r))运行命令(即在事务之外)。要解决此问题,您需要使用事务(例如 err := tx.PutObj(&r))。

【讨论】:

    猜你喜欢
    • 2020-08-06
    • 2011-09-19
    • 2013-12-01
    • 2012-07-13
    • 2013-12-15
    • 2011-01-26
    • 2017-03-03
    • 2012-03-10
    相关资源
    最近更新 更多