Golang使用事务的简单实践

作者:

文章目录
  • 事务具备 ACID 四大特性: A(Atomicity,原子性):事务中的操作要么全部成功,要么全部失败。 C(Consistency,一致性):事务执行前后,数据必须保持一致。 I(Isolation,隔离性):多个事务之间相互独立,互不干扰。 D(Durability,持久性):事务一旦提交,数据就会被永久保存。
  • // 需要使用事务的方法 func (s *userService) funcName(ctx context.Context, req *v1.Req) (*v1.RespData, error) { // 获取事务的最终结果 err := s.tm.Transaction(ctx, func(ctx context.Context) error { // 内部写相关的原子性数据库操作 // 如果任意操作报错,将触发回滚,恢复之前的状态 // 调用数据层方法 repository // repository.CreateUser(ctx, req) // repository.CreateLog(ctx, req) // 所有操作均无错误,正常退出 return nil }) // 如果事务中存在错误,所有操作都会被回滚 if err != nil { return nil, err } // 没有触发事务报错,正常返回结果 return &v1.RespData{}, nil }
  • 如果你使用的是 GORM,事务的写法会更简洁: func (s *userService) CreateOrder(ctx context.Context, req *v1.OrderReq) error { return s.db.Transaction(func(tx *gorm.DB) error { // 创建订单 if err := tx.Create(&Order{UserID: req.UserID, Amount: req.Amount}).Error; err != nil { return err // 返回错误会触发回滚 } // 扣减库存 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 } // 所有操作成功,事务提交 return nil }) }
  • 用户注册:写入用户表 + 写入用户详情表 + 写入日志表。 订单支付:扣减库存 + 生成订单记录 + 写入支付流水。 资金转账:账户 A 扣款 + 账户 B 加款 + 生成转账记录。
  • 事务粒度要小:只包含必要的数据库操作,避免长时间占用连接。 错误处理要及时:一旦事务中出现错误,应立即返回,触发回滚。 避免耗时操作:不要在事务中调用外部 API 或执行复杂计算。 封装事务逻辑:在服务层统一封装事务,减少重复代码。 结合 Context:在事务中传递 context.Context,方便控制超时和取消。
  • Q1:事务中如何传递上下文(Context)?在事务回调函数中继续传递 ctx,保证日志、超时控制等功能生效。例如: s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { return tx.Create(&User{Name: “Tom”}).Error }) Q2:如何在事务中调用多个 repository?只需将事务对象 tx 传递给 repository 方法即可: func (r *UserRepo) Create(ctx context.Context, tx *gorm.DB, user *User) error { return tx.WithContext(ctx).Create(user).Error } 这样可以保证所有 repository 操作都在同一个事务中。 Q3:事务中能否执行外部 API 调用?不推荐。外部 API 调用可能耗时较长,导致事务长时间占用数据库连接,影响性能。建议先执行事务,再调用外部服务,或通过消息队列解耦。 Q4:如何处理事务嵌套?GORM 默认不支持真正的嵌套事务,但可以使用 SavePoint 和 RollbackTo 来模拟: tx.SavePoint(“sp1”) // … tx.RollbackTo(“sp1”)
  • 事务是保证数据一致性的重要手段。在 Golang 项目中,我们可以通过事务管理器或 GORM 的 db.Transaction 来简化事务的使用。 只要遵循 小粒度、快执行、及时回滚 的原则,就能在项目中高效、安全地使用事务。 到此这篇关于Golang 使用事务的简单实践的文章就介绍到这了,更多相关Golang 事务内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: Golang在整洁架构基础上实现事务操作 Golang实现事务型内存数据库的方法详解 golang gorm错误处理事务以及日志用法示例 golang操作连接数据库实现mysql事务示例 golang实现mysql数据库事务的提交与回滚 golang如何优雅的编写事务代码示例
  • 目录
    • 一、事务的基本概念
    • 二、事务的使用示例
    • 三、结合 GORM 使用事务
    • 四、事务的应用场景
    • 五、最佳实践
    • 六、常见问题 FAQ
    • 七、总结

    在实际业务开发中,事务(Transaction)是保证数据一致性的重要手段。比如:

    • 用户注册时,需要同时写入用户表和日志表;
    • 订单支付时,需要同时扣减库存和生成支付流水;
    • 转账时,需要同时扣减账户 A 的余额并增加账户 B 的余额。

    这些操作必须 要么全部成功,要么全部失败,否则就会导致数据不一致。本文将结合 Golang 的示例代码,介绍如何在项目中优雅地使用事务。

    事务具备 ACID 四大特性:

    • A(Atomicity,原子性):事务中的操作要么全部成功,要么全部失败。
    • C(Consistency,一致性):事务执行前后,数据必须保持一致。
    • I(Isolation,隔离性):多个事务之间相互独立,互不干扰。
    • D(Durability,持久性):事务一旦提交,数据就会被永久保存。

    // 需要使用事务的方法
    func (s *userService) funcName(ctx context.Context, req *v1.Req) (*v1.RespData, error) {
        // 获取事务的最终结果
        err := s.tm.Transaction(ctx, func(ctx context.Context) error {
            // 内部写相关的原子性数据库操作
            // 如果任意操作报错,将触发回滚,恢复之前的状态
    
            // 调用数据层方法 repository
            // repository.CreateUser(ctx, req)
            // repository.CreateLog(ctx, req)
    
            // 所有操作均无错误,正常退出
            return nil
        })
    
        // 如果事务中存在错误,所有操作都会被回滚
        if err != nil {
            return nil, err
        }
    
        // 没有触发事务报错,正常返回结果
        return &v1.RespData{}, nil
    }
    

    如果你使用的是 GORM,事务的写法会更简洁:

    func (s *userService) CreateOrder(ctx context.Context, req *v1.OrderReq) error {
        return s.db.Transaction(func(tx *gorm.DB) error {
            // 创建订单
            if err := tx.Create(&Order{UserID: req.UserID, Amount: req.Amount}).Error; err != nil {
                return err // 返回错误会触发回滚
            }
    
            // 扣减库存
            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
            }
    
            // 所有操作成功,事务提交
            return nil
        })
    }
    

    • 用户注册:写入用户表 + 写入用户详情表 + 写入日志表。
    • 订单支付:扣减库存 + 生成订单记录 + 写入支付流水。
    • 资金转账:账户 A 扣款 + 账户 B 加款 + 生成转账记录。

    1. 事务粒度要小:只包含必要的数据库操作,避免长时间占用连接。
    2. 错误处理要及时:一旦事务中出现错误,应立即返回,触发回滚。
    3. 避免耗时操作:不要在事务中调用外部 API 或执行复杂计算。
    4. 封装事务逻辑:在服务层统一封装事务,减少重复代码。
    5. 结合 Context:在事务中传递 context.Context,方便控制超时和取消。

    Q1:事务中如何传递上下文(Context)?
    在事务回调函数中继续传递 ctx,保证日志、超时控制等功能生效。例如:

    s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
        return tx.Create(&User{Name: "Tom"}).Error
    })
    

    Q2:如何在事务中调用多个 repository?
    只需将事务对象 tx 传递给 repository 方法即可:

    func (r *UserRepo) Create(ctx context.Context, tx *gorm.DB, user *User) error {
        return tx.WithContext(ctx).Create(user).Error
    }
    

    这样可以保证所有 repository 操作都在同一个事务中。

    Q3:事务中能否执行外部 API 调用?
    不推荐。外部 API 调用可能耗时较长,导致事务长时间占用数据库连接,影响性能。建议先执行事务,再调用外部服务,或通过消息队列解耦。

    Q4:如何处理事务嵌套?
    GORM 默认不支持真正的嵌套事务,但可以使用 SavePointRollbackTo 来模拟:

    tx.SavePoint("sp1")
    // ...
    tx.RollbackTo("sp1")
    

    事务是保证数据一致性的重要手段。在 Golang 项目中,我们可以通过事务管理器或 GORM 的 db.Transaction 来简化事务的使用。

    只要遵循 小粒度、快执行、及时回滚 的原则,就能在项目中高效、安全地使用事务。

    到此这篇关于Golang 使用事务的简单实践的文章就介绍到这了,更多相关Golang 事务内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • Golang在整洁架构基础上实现事务操作
    • Golang实现事务型内存数据库的方法详解
    • golang gorm错误处理事务以及日志用法示例
    • golang操作连接数据库实现mysql事务示例
    • golang实现mysql数据库事务的提交与回滚
    • golang如何优雅的编写事务代码示例

    站内搜索