Golang 使用事務(wù)的簡單實踐
在實際業(yè)務(wù)開發(fā)中,事務(wù)(Transaction)是保證數(shù)據(jù)一致性的重要手段。比如:
- 用戶注冊時,需要同時寫入用戶表和日志表;
- 訂單支付時,需要同時扣減庫存和生成支付流水;
- 轉(zhuǎn)賬時,需要同時扣減賬戶 A 的余額并增加賬戶 B 的余額。
這些操作必須 要么全部成功,要么全部失敗,否則就會導(dǎo)致數(shù)據(jù)不一致。本文將結(jié)合 Golang 的示例代碼,介紹如何在項目中優(yōu)雅地使用事務(wù)。
一、事務(wù)的基本概念
事務(wù)具備 ACID 四大特性:
- A(Atomicity,原子性):事務(wù)中的操作要么全部成功,要么全部失敗。
- C(Consistency,一致性):事務(wù)執(zhí)行前后,數(shù)據(jù)必須保持一致。
- I(Isolation,隔離性):多個事務(wù)之間相互獨立,互不干擾。
- D(Durability,持久性):事務(wù)一旦提交,數(shù)據(jù)就會被永久保存。
二、事務(wù)的使用示例
// 需要使用事務(wù)的方法
func (s *userService) funcName(ctx context.Context, req *v1.Req) (*v1.RespData, error) {
// 獲取事務(wù)的最終結(jié)果
err := s.tm.Transaction(ctx, func(ctx context.Context) error {
// 內(nèi)部寫相關(guān)的原子性數(shù)據(jù)庫操作
// 如果任意操作報錯,將觸發(fā)回滾,恢復(fù)之前的狀態(tài)
// 調(diào)用數(shù)據(jù)層方法 repository
// repository.CreateUser(ctx, req)
// repository.CreateLog(ctx, req)
// 所有操作均無錯誤,正常退出
return nil
})
// 如果事務(wù)中存在錯誤,所有操作都會被回滾
if err != nil {
return nil, err
}
// 沒有觸發(fā)事務(wù)報錯,正常返回結(jié)果
return &v1.RespData{}, nil
}
三、結(jié)合 GORM 使用事務(wù)
如果你使用的是 GORM,事務(wù)的寫法會更簡潔:
func (s *userService) CreateOrder(ctx context.Context, req *v1.OrderReq) error {
return s.db.Transaction(func(tx *gorm.DB) error {
// 創(chuàng)建訂單
if err := tx.Create(&Order{UserID: req.UserID, Amount: req.Amount}).Error; err != nil {
return err // 返回錯誤會觸發(fā)回滾
}
// 扣減庫存
if err := tx.Model(&Product{}).
Where("id = ? AND stock >= ?", req.ProductID, req.Quantity).
Update("stock", gorm.Expr("stock - ?", req.Quantity)).Error; err != nil {
return err
}
// 寫入日志
if err := tx.Create(&Log{Action: "create_order", UserID: req.UserID}).Error; err != nil {
return err
}
// 所有操作成功,事務(wù)提交
return nil
})
}
四、事務(wù)的應(yīng)用場景
- 用戶注冊:寫入用戶表 + 寫入用戶詳情表 + 寫入日志表。
- 訂單支付:扣減庫存 + 生成訂單記錄 + 寫入支付流水。
- 資金轉(zhuǎn)賬:賬戶 A 扣款 + 賬戶 B 加款 + 生成轉(zhuǎn)賬記錄。
五、最佳實踐
- 事務(wù)粒度要小:只包含必要的數(shù)據(jù)庫操作,避免長時間占用連接。
- 錯誤處理要及時:一旦事務(wù)中出現(xiàn)錯誤,應(yīng)立即返回,觸發(fā)回滾。
- 避免耗時操作:不要在事務(wù)中調(diào)用外部 API 或執(zhí)行復(fù)雜計算。
- 封裝事務(wù)邏輯:在服務(wù)層統(tǒng)一封裝事務(wù),減少重復(fù)代碼。
- 結(jié)合 Context:在事務(wù)中傳遞
context.Context,方便控制超時和取消。
六、常見問題 FAQ
Q1:事務(wù)中如何傳遞上下文(Context)?
在事務(wù)回調(diào)函數(shù)中繼續(xù)傳遞 ctx,保證日志、超時控制等功能生效。例如:
s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
return tx.Create(&User{Name: "Tom"}).Error
})
Q2:如何在事務(wù)中調(diào)用多個 repository?
只需將事務(wù)對象 tx 傳遞給 repository 方法即可:
func (r *UserRepo) Create(ctx context.Context, tx *gorm.DB, user *User) error {
return tx.WithContext(ctx).Create(user).Error
}
這樣可以保證所有 repository 操作都在同一個事務(wù)中。
Q3:事務(wù)中能否執(zhí)行外部 API 調(diào)用?
不推薦。外部 API 調(diào)用可能耗時較長,導(dǎo)致事務(wù)長時間占用數(shù)據(jù)庫連接,影響性能。建議先執(zhí)行事務(wù),再調(diào)用外部服務(wù),或通過消息隊列解耦。
Q4:如何處理事務(wù)嵌套?
GORM 默認不支持真正的嵌套事務(wù),但可以使用 SavePoint 和 RollbackTo 來模擬:
tx.SavePoint("sp1")
// ...
tx.RollbackTo("sp1")
七、總結(jié)
事務(wù)是保證數(shù)據(jù)一致性的重要手段。在 Golang 項目中,我們可以通過事務(wù)管理器或 GORM 的 db.Transaction 來簡化事務(wù)的使用。
只要遵循 小粒度、快執(zhí)行、及時回滾 的原則,就能在項目中高效、安全地使用事務(wù)。
到此這篇關(guān)于Golang 使用事務(wù)的簡單實踐的文章就介紹到這了,更多相關(guān)Golang 事務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang通道阻塞情況與通道無阻塞實現(xiàn)小結(jié)
本文主要介紹了Golang通道阻塞情況與通道無阻塞實現(xiàn)小結(jié),詳細解析了通道的類型、操作方法以及垃圾回收機制,從基礎(chǔ)概念到高級應(yīng)用,具有一定的參考價值,感興趣的可以了解一下2024-03-03
Golang創(chuàng)建構(gòu)造函數(shù)的方法超詳細講解
構(gòu)造器一般面向?qū)ο笳Z言的典型特性,用于初始化變量。Go語言沒有任何具體構(gòu)造器,但我們能使用該特性去初始化變量。本文介紹不同類型構(gòu)造器的差異及其應(yīng)用場景2023-01-01

