英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论