一、普通写法
这种写法非常朴实,程序流程也非常明确,但是事务处理与程序流程嵌入太深,容易遗漏,造成严重的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
func DoSomething() (err error) {
tx, err := db.Begin()
if err != nil {
return
}
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p) // re-throw panic after Rollback
}
}()
if _, err = tx.Exec(...); err != nil {
tx.Rollback()
return
}
if _, err = tx.Exec(...); err != nil {
tx.Rollback()
return
}
// ...
err = tx.Commit()
return
}
|
二、函数域 defer
下面这种写法把事务处理从程序流程抽离了出来,不容易遗漏,但是作用域是整个函数,程序流程不是很清晰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
func DoSomething() (err error) {
tx, err := db.Begin()
if err != nil {
return
}
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p) // re-throw panic after Rollback
} else if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()
if _, err = tx.Exec(...); err != nil {
return
}
if _, err = tx.Exec(...); err != nil {
return
}
// ...
return
}
|
三、事务函数
写法三是对写法二的进一步封装,写法高级一点,缺点同上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
func Transact(db *sql.DB, txFunc func(*sql.Tx) error) (err error) {
tx, err := db.Begin()
if err != nil {
return
}
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p) // re-throw panic after Rollback
} else if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()
err = txFunc(tx)
return err
}
func DoSomething() error {
return Transact(db, func (tx *sql.Tx) error {
if _, err := tx.Exec(...); err != nil {
return err
}
if _, err := tx.Exec(...); err != nil {
return err
}
})
}
|
四、我的写法
经过总结和实验,我采用了下面这种写法,defer tx.Rollback() 使得事务回滚始终得到执行。
当 tx.Commit() 执行后,tx.Rollback() 起到关闭事务的作用,
当程序因为某个错误中止,tx.Rollback() 起到回滚事务,同时关闭事务的作用。
Tips: 标准库的 Rollback、Commit 会自动回收连接,所以不需要调用 Close 方法。
1 针对普通场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func DoSomething() (err error) {
tx, _ := db.Begin()
defer tx.Rollback()
if _, err = tx.Exec(...); err != nil {
return
}
if _, err = tx.Exec(...); err != nil {
return
}
// ...
err = tx.Commit()
return
}
|
2 针对循环场景
- 小事务
- 每次循环提交一次
- 在循环内部使用这种写法的时候,defer 不能使用,所以要把事务部分抽离到独立的函数当中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
func DoSomething() (err error) {
tx, _ := db.Begin()
defer tx.Rollback()
if _, err = tx.Exec(...); err != nil {
return
}
if _, err = tx.Exec(...); err != nil {
return
}
...
err = tx.Commit()
return
}
for {
if err := DoSomething(); err != nil{
...
}
}
|
- 大事务
- 批量提交
- 大事务的场景和普通场景是一样的,没有任何区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func DoSomething() (err error) {
tx, _ := db.Begin()
defer tx.Rollback()
for{
if _, err = tx.Exec(...); err != nil {
return
}
if _, err = tx.Exec(...); err != nil {
return
}
// ...
}
err = tx.Commit()
return
}
|