在事务进行到一半时,如何轻松地提交工作并继续进行?

huangapple go评论107阅读模式
英文:

What is an easy way to commit work half way through a transaction, but then continue to

问题

背景

我正在使用github.com/jmoiron/sqlx的Golang包与Postgres数据库进行交互。

我有以下的包装函数来在事务中运行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: 进行一些数据库操作
        
        if err := tx.Commit(); err != nil {
            return err
        }
        
        // Point B: 进行更多的数据库操作,可能会返回错误
    })
}

期望的行为

  • 如果在Point A处发生错误,则事务应该没有执行任何操作。
  • 如果在Point B处发生错误,则事务仍然应该完成Point A处的操作。

当前代码的问题

目前的代码无法按照预期工作,因为我在runInTransaction函数中提交了事务,而在SampleFunc函数中又提交了一次。

一个可能的解决方案

在提交事务的地方,我可以改为执行类似于tx.Exec("SAVEPOINT my_savepoint"),然后使用defer tx.Exec("ROLLBACK TO SAVEPOINT my_savepoint")进行回滚。

在Point B的代码之后,我可以运行:tx.Exec("RELEASE SAVEPOINT my_savepoint")

因此,如果Point B的代码运行没有错误,我将无法回滚到保存点。

可能解决方案的问题

我不确定使用保存点是否会影响database/sql包的行为。此外,我的解决方案似乎有些混乱,肯定有更简洁的方法来实现这个目标!

英文:

Background

I am using the github.com/jmoiron/sqlx golang package with a Postgres database.

I have the following wrapper function to run SQL code in a transaction:

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
}

Given this, consider the following code:

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
    })
}

Desired behavior

  • If there is an error at Point A, then the transaction should have done zero work
  • If there is an error at Point B, then the transaction should still have completed the work at Point A.

Problem with current code

The code does not work as intended at the moment, because I am committing the transaction twice (once in runInTransaction, once in SampleFunc).

A Possible Solution

Where I commit the transaction, I could instead run something like tx.Exec("SAVEPOINT my_savepoint"), then defer tx.Exec("ROLLBACK TO SAVEPOINT my_savepoint")

After the code at Point B, I could run: tx.Exec("RELEASE SAVEPOINT my_savepoint")

So, if the code at Point B runs without error, I will fail to ROLLBACK to my savepoint.

Problems with Possible Solution

I'm not sure if using savepoints will mess with the database/sql package's behavior. Also, my solution seems a bit messy -- surely there is a cleaner way to do this!

答案1

得分: 2

多个事务

您可以将工作分为两个事务:

func (s *store) SampleFunc(ctx context.Context) error {
    err := s.runInTransaction(ctx, func(tx *sqlx.Tx) error {
        // 点A:进行一些数据库操作
    })
    if err != nil {
        return err
    }
    return s.runInTransaction(ctx, func(tx *sqlx.Tx) error {
        // 点B:进行更多的数据库操作,可能会返回错误
    })
}
英文:

Multiple transactions

You can split your work in two transactions:

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
    })
}

答案2

得分: 1

我遇到了类似的问题:在一个事务中有很多步骤。
在开始事务后:

  • BEGIN

  • 在循环中:

  • SAVEPOINT s1

  • 一些操作....

  • 如果出现错误:ROLLBACK TO SAVEPOINT s1

  • 如果没有问题,继续下一步

  • 最后 COMMIT

这种方法使我能够逐步执行所有步骤。如果某些步骤失败,我可以只丢弃它们,保留其他步骤。最后提交所有“良好”的工作。

英文:

I had the problem alike: I had a lots of steps in one transaction.
After starting transaction:

  • BEGIN

  • In loop:

  • SAVEPOINT s1

  • Some actions ....

  • If I get an error: ROLLBACK TO SAVEPOINT s1

  • If OK go to next step

  • Finally COMMIT

This approach gives me ability to perform all steps one-by-one. If some steps got failed I can throw away only them, keeping others. And finally commit all "good" work.

huangapple
  • 本文由 发表于 2017年7月1日 15:36:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/44858531.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定