Golang transaction 事務(wù)使用的正確姿勢(shì)
本文中作者展示了 golang 事務(wù)的三種寫法。
第一種寫法
這種寫法非常樸實(shí),程序流程也非常明確,但是事務(wù)處理與程序流程嵌入太深,容易遺漏,造成嚴(yán)重的問題
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
}
第二種寫法
下面這種寫法把事務(wù)處理從程序流程抽離了出來,不容易遺漏,但是作用域是整個(gè)函數(shù),程序流程不是很清晰
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
}
第三種寫法
寫法三是對(duì)寫法二的進(jìn)一步封裝,寫法高級(jí)一點(diǎn),缺點(diǎn)同上
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
}
})
}
我的寫法
經(jīng)過總結(jié)和實(shí)驗(yàn),我采用了下面這種寫法,defer tx.Rollback() 使得事務(wù)回滾始終得到執(zhí)行。當(dāng) tx.Commit() 執(zhí)行后,tx.Rollback() 起到關(guān)閉事務(wù)的作用, 當(dāng)程序因?yàn)槟硞€(gè)錯(cuò)誤中止,tx.Rollback() 起到回滾事務(wù),同事關(guān)閉事務(wù)的作用。
普通場(chǎng)景
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
}
循環(huán)場(chǎng)景
(1) 小事務(wù) 每次循環(huán)提交一次 在循環(huán)內(nèi)部使用這種寫法的時(shí)候,defer 不能使用,所以要把事務(wù)部分抽離到獨(dú)立的函數(shù)當(dāng)中
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{
// ...
}
}
(2) 大事務(wù) 批量提交 大事務(wù)的場(chǎng)景和普通場(chǎng)景是一樣的,沒有任何區(qū)別
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
}