乐闻世界logo
搜索文章和话题

GORM 中如何使用事务?

3月6日 21:37

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() - 开始事务

go
tx := db.Begin()

Commit() - 提交事务

go
tx.Commit()

Rollback() - 回滚事务

go
tx.Rollback()

RollbackTo() - 回滚到保存点

go
// 创建保存点 tx.SavePoint("sp1") // 回滚到保存点 tx.RollbackTo("sp1")

完整的事务示例

银行转账示例

go
func 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() 方法

go
err := 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 { // 事务失败 }

嵌套事务

go
err := 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 }) })

事务选项

设置事务隔离级别

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelSerializable, })

只读事务

go
tx := db.Begin(&sql.TxOptions{ ReadOnly: true, })

事务中的常见操作

创建记录

go
tx.Create(&user)

更新记录

go
tx.Model(&user).Update("name", "John")

删除记录

go
tx.Delete(&user)

查询记录

go
tx.First(&user, 1)

原生 SQL

go
tx.Exec("UPDATE users SET name = ?", "John")

事务错误处理

检查事务状态

go
tx := 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 }

事务超时处理

go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() tx := db.BeginTx(ctx, nil) // 执行操作...

嵌套事务和保存点

创建保存点

go
tx.SavePoint("sp1")

回滚到保存点

go
tx.RollbackTo("sp1")

释放保存点

go
tx.Exec("RELEASE SAVEPOINT sp1")

注意事项

  1. 事务范围:事务应该在尽可能小的范围内使用,减少锁定时间
  2. 错误处理:必须正确处理事务中的错误,确保失败时回滚
  3. 资源释放:使用 defer 确保事务在函数退出时被正确处理
  4. 隔离级别:根据业务需求选择合适的事务隔离级别
  5. 死锁预防:避免长时间持有锁,按固定顺序访问资源
  6. 性能考虑:事务会增加数据库开销,避免不必要的事务

最佳实践

  1. 使用 Transaction() 回调:简化事务处理代码
  2. 保持简短:事务应该尽可能简短快速
  3. 错误处理:始终检查并处理错误
  4. 日志记录:记录事务的开始、提交和回滚
  5. 测试覆盖:为事务逻辑编写充分的测试用例
  6. 避免嵌套:尽量避免过深的嵌套事务

事务隔离级别

Read Uncommitted(读未提交)

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelReadUncommitted, })

Read Committed(读已提交)

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelReadCommitted, })

Repeatable Read(可重复读)

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelRepeatableRead, })

Serializable(串行化)

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelSerializable, })
标签:Gorm