Gorm(十一)事务
在 Gorm 中,事务(Transaction)用于保证一系列数据库操作的原子性(要么全部成功,要么要么全部失败),避免部分操作成功、部分失败导致的数据不一致。Gorm 支持手动事务、自动事务和嵌套事务,适用于不同场景。以下是详细说明:
一、手动事务(Manual Transactions)
手动事务需要显式调用 Begin 开启事务,通过 Commit 提交事务,或 Rollback 回滚事务,全程由开发者控制事务生命周期,适合复杂业务逻辑(如多步操作需手动判断是否提交)。
基本流程
- 开启事务:调用
db.Begin()获取事务对象tx *gorm.DB。 - 执行操作:通过事务对象
tx执行数据库操作(如Create、Update、Delete)。 - 判断结果:若所有操作成功,调用
tx.Commit()提交;若失败,调用tx.Rollback()回滚。
示例:转账业务(扣A向B转账,需保证扣钱和加钱原子性)
// 定义模型
type User struct {gorm.ModelName stringBalance int // 余额
}// 手动事务实现转账
func transfer(db *gorm.DB, fromID, toID, amount int) error {// 1. 开启事务tx := db.Begin()if tx.Error != nil {return tx.Error // 开启事务失败(如数据库连接问题)}// 2. 执行事务内操作var fromUser, toUser User// 查A的账户if err := tx.First(&fromUser, fromID).Error; err != nil {tx.Rollback() // 查询失败,回滚return err}// 查B的账户if err := tx.First(&toUser, toID).Error; err != nil {tx.Rollback()return err}// A的余额不足,回滚if fromUser.Balance < amount {tx.Rollback()return fmt.Errorf("余额不足")}// A扣钱if err := tx.Model(&fromUser).Update("balance", fromUser.Balance - amount).Error; err != nil {tx.Rollback()return err}// B加钱if err := tx.Model(&toUser).Update("balance", toUser.Balance + amount).Error; err != nil {tx.Rollback()return err}// 3. 所有操作成功,提交事务return tx.Commit().Error
}
关键特性
- 显式控制:开发者完全掌控事务的开启、提交、回滚,适合需要中间判断(如余额校验)的场景。
- 事务对象
tx:事务内所有操作必须使用tx执行,而非原db对象,否则操作会脱离事务控制。 - 错误处理:任何任何一步操作失败,必须调用
tx.Rollback(),否则事务可能长期占用资源。
二、自动事务(Auto Transactions)
自动事务通过 db.Transaction 方法封装事务逻辑,传入一个函数作为事务内的操作,Gorm 会自动开启事务、执行函数、根据函数返回值决定提交或回滚,简化代码。
基本用法
// 自动事务实现转账
func transferAuto(db *gorm.DB, fromID, toID, amount int) error {// 调用 db.Transaction,传入事务内操作的函数return db.Transaction(func(tx *gorm.DB) error {var fromUser, toUser User// 查A的账户if err := tx.First(&fromUser, fromID).Error; err != nil {return err // 返回错误,Gorm 自动回滚}// 查B的账户if err := tx.First(&toUser, toID).Error; err != nil {return err}// 余额不足,返回错误if fromUser.Balance < amount {return fmt.Errorf("余额不足")}// A扣钱if err := tx.Model(&fromUser).Update("balance", fromUser.Balance - amount).Error; err != nil {return err}// B加钱if err := tx.Model(&toUser).Update("balance", toUser.Balance + amount).Error; err != nil {return err}return nil // 无错误,Gorm 自动提交})
}
关键特性
- 自动处理:无需手动调用
Begin/Commit/Rollback,函数返回nil则提交,返回error则回滚。 - 简化代码:减少模板代码,适合逻辑相对简单、无需复杂中间判断的事务场景。
- 等价性:功能与手动事务一致,只是将事务控制交给 Gorm 自动处理。
三、嵌套事务(Nested Transactions)
嵌套事务指在一个事务内部开启另一个事务,外层事务称为“父事务”,内层称为“子事务”。Gorm 通过数据库的 SAVEPOINT(保存点)实现嵌套事务,支持内层事务独立回滚而不影响外层。
基本用法
// 嵌套事务示例
func nestedTransaction(db *gorm.DB) error {return db.Transaction(func(parentTx *gorm.DB) error {// 父事务:创建用户AuserA := User{Name: "Alice", Balance: 1000}if err := parentTx.Create(&userA).Error; err != nil {return err // 父事务回滚}// 开启子事务(嵌套事务)if err := parentTx.Transaction(func(childTx *gorm.DB) error {// 子事务:创建用户BuserB := User{Name: "Bob", Balance: 500}if err := childTx.Create(&userB).Error; err != nil {return err // 子事务回滚(仅回滚自身操作,不影响父事务的userA)}// 子事务内主动回滚(例如业务判断失败)return fmt.Errorf("子事务主动回滚") // 仅子事务回滚,userB 不会被创建}); err != nil {fmt.Printf("子事务失败:%v,但父事务可继续\n", err)}// 父事务继续执行:更新用户A的余额return parentTx.Model(&userA).Update("balance", 2000).Error})
}
实现原理
- 保存点(SAVEPOINT):子事务开启时,Gorm 会在父事务中创建一个保存点(如
SAVEPOINT sp1)。 - 子事务回滚:子事务失败时,仅回滚到保存点(
ROLLBACK TO sp1),不影响父事务之前的操作。 - 父事务提交:只有当父事务最终提交时,所有未回滚的子事务操作才会生效。
关键特性
- 局部回滚:子事务回滚仅影响自身操作,父事务可继续执行或独立回滚,适合复杂业务中“部分步骤允许失败”的场景。
- 数据库支持:依赖数据库对
SAVEPOINT的支持(如 MySQL、PostgreSQL 支持,SQLite 部分支持)。 - 嵌套层级:理论上支持多层嵌套,但层级过深可能影响性能,建议控制在 2-3 层以内。
四、事务的注意事项
- 操作必须使用事务对象:事务内的所有数据库操作(
Create/Update/Find等)必须通过tx执行,否则会绕过事务,导致数据不一致。 - 锁冲突与超时:长事务可能导致锁冲突(如并发修改同一行),建议事务执行时间尽可能短。
- 索引与性能:事务内的查询应确保走索引,避免全表扫描导致事务阻塞。
- 嵌套事务的限制:子事务无法单独提交,必须依赖父事务最终提交;若父事务回滚,所有子事务操作都会被回滚。
总结
- 手动事务:显式控制
Begin/Commit/Rollback,适合复杂逻辑和中间判断。 - 自动事务:通过
db.Transaction自动管理事务,简化代码,适合简单场景。 - 嵌套事务:基于保存点实现,支持子事务局部回滚,适合复杂业务中部分步骤可失败的场景。
根据业务复杂度和灵活性需求选择合适的事务方式,核心目标是保证数据操作的原子性。
