GORM 提供了强大的事务支持,可以确保多个数据库操作的原子性和一致性。
基本事务操作
自动事务
GORM 默认会在单个操作中自动管理事务:
go// 单个操作自动使用事务 db.Create(&user) db.Save(&user) db.Delete(&user)
手动事务
对于需要多个操作的场景,需要手动管理事务:
go// 开始事务 tx := db.Begin() // 执行操作 if err := tx.Create(&user).Error; err != nil { // 发生错误,回滚事务 tx.Rollback() return err } if err := tx.Create(&profile).Error; err != nil { tx.Rollback() return err } // 提交事务 tx.Commit()
事务方法
Begin() - 开始事务
gotx := db.Begin()
Commit() - 提交事务
gotx.Commit()
Rollback() - 回滚事务
gotx.Rollback()
RollbackTo() - 回滚到保存点
go// 创建保存点 tx.SavePoint("sp1") // 回滚到保存点 tx.RollbackTo("sp1")
完整的事务示例
银行转账示例
gofunc TransferMoney(db *gorm.DB, fromID, toID uint, amount float64) error { tx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() // 查询转出账户 var fromAccount Account if err := tx.Where("id = ?", fromID).First(&fromAccount).Error; err != nil { tx.Rollback() return err } // 检查余额是否足够 if fromAccount.Balance < amount { tx.Rollback() return errors.New("余额不足") } // 扣除转出账户余额 if err := tx.Model(&fromAccount).Update("balance", fromAccount.Balance-amount).Error; err != nil { tx.Rollback() return err } // 查询转入账户 var toAccount Account if err := tx.Where("id = ?", toID).First(&toAccount).Error; err != nil { tx.Rollback() return err } // 增加转入账户余额 if err := tx.Model(&toAccount).Update("balance", toAccount.Balance+amount).Error; err != nil { tx.Rollback() return err } // 记录交易日志 transaction := Transaction{ FromAccountID: fromID, ToAccountID: toID, Amount: amount, Status: "completed", } if err := tx.Create(&transaction).Error; err != nil { tx.Rollback() return err } // 提交事务 return tx.Commit().Error }
事务回调
GORM 提供了事务回调方法,可以简化事务处理:
Transaction() 方法
goerr := db.Transaction(func(tx *gorm.DB) error { // 在事务中执行操作 if err := tx.Create(&user).Error; err != nil { // 返回错误会自动回滚 return err } if err := tx.Create(&profile).Error; err != nil { return err } // 返回 nil 会自动提交 return nil }) if err != nil { // 事务失败 }
嵌套事务
goerr := db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&user).Error; err != nil { return err } // 嵌套事务 return tx.Transaction(func(tx2 *gorm.DB) error { return tx2.Create(&profile).Error }) })
事务选项
设置事务隔离级别
gotx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelSerializable, })
只读事务
gotx := db.Begin(&sql.TxOptions{ ReadOnly: true, })
事务中的常见操作
创建记录
gotx.Create(&user)
更新记录
gotx.Model(&user).Update("name", "John")
删除记录
gotx.Delete(&user)
查询记录
gotx.First(&user, 1)
原生 SQL
gotx.Exec("UPDATE users SET name = ?", "John")
事务错误处理
检查事务状态
gotx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() // 执行操作 if err := tx.Create(&user).Error; err != nil { tx.Rollback() log.Printf("事务失败: %v", err) return err } // 提交事务 if err := tx.Commit().Error; err != nil { log.Printf("提交失败: %v", err) return err }
事务超时处理
goctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() tx := db.BeginTx(ctx, nil) // 执行操作...
嵌套事务和保存点
创建保存点
gotx.SavePoint("sp1")
回滚到保存点
gotx.RollbackTo("sp1")
释放保存点
gotx.Exec("RELEASE SAVEPOINT sp1")
注意事项
- 事务范围:事务应该在尽可能小的范围内使用,减少锁定时间
- 错误处理:必须正确处理事务中的错误,确保失败时回滚
- 资源释放:使用 defer 确保事务在函数退出时被正确处理
- 隔离级别:根据业务需求选择合适的事务隔离级别
- 死锁预防:避免长时间持有锁,按固定顺序访问资源
- 性能考虑:事务会增加数据库开销,避免不必要的事务
最佳实践
- 使用 Transaction() 回调:简化事务处理代码
- 保持简短:事务应该尽可能简短快速
- 错误处理:始终检查并处理错误
- 日志记录:记录事务的开始、提交和回滚
- 测试覆盖:为事务逻辑编写充分的测试用例
- 避免嵌套:尽量避免过深的嵌套事务
事务隔离级别
Read Uncommitted(读未提交)
gotx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelReadUncommitted, })
Read Committed(读已提交)
gotx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelReadCommitted, })
Repeatable Read(可重复读)
gotx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelRepeatableRead, })
Serializable(串行化)
gotx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelSerializable, })