Gorm多协程同事务更新多表时随机触发bad connection错误
解决同一事务多goroutine下的
driver: bad connection错误 问题背景
在同一事务中使用多个goroutine并发更新多张表时,随机出现driver: bad connection错误,首次调用必成功,后续交替出现失败与成功。已升级Gorm及PostgreSQL驱动至最新版本,问题仍未解决。
核心原因
Gorm的事务对象(*gorm.DB实例)并非线程安全,它绑定了单个PostgreSQL连接,并发操作会导致连接状态混乱,从而触发连接异常。你的代码中通过errgroup.Group启动多个goroutine,共享同一个注入到context的事务对象,这直接违反了事务对象的使用规范。
代码问题点
你的事务执行代码中,所有goroutine共享txCtx中的同一个事务对象,并发执行数据库操作导致连接状态冲突:
err = transaction.WithTransaction(context.Background(), func(txCtx context.Context) error { errorGroup := &errgroup.Group{} errorGroup.Go(func() error { return s.addTotable1(txCtx, *model) }) errorGroup.Go(func() error { return s.updateTable1(txCtx, *model) }) // 其他goroutine操作... if err := errorGroup.Wait(); err != nil { transactionError = err return err } }, func(trCtx context.Context) error { return transactionError })
修复方案
方案1:改为串行执行(推荐)
事务内的操作必须在同一个goroutine中执行,因此去掉所有goroutine,按顺序执行数据库操作:
err = transaction.WithTransaction(context.Background(), func(txCtx context.Context) error { if err := s.addTotable1(txCtx, *model); err != nil { transactionError = err return err } if err := s.updateTable1(txCtx, *model); err != nil { transactionError = err return err } if err := s.updateTable2(txCtx, *model); err != nil { transactionError = err return err } if err := s.updateTable3(txCtx, *model); err != nil { transactionError = err return err } if err := s.updateTable4(txCtx, *model); err != nil { transactionError = err return err } if err := s.updateTable5(txCtx, *model); err != nil { transactionError = err return err } return nil }, func(trCtx context.Context) error { return transactionError })
方案2:独立事务+手动协调(仅适用于非强一致场景)
如果业务必须并发执行,且能放弃单事务的原子性,可让每个goroutine使用独立事务,执行完成后统一确认结果:
err = transaction.WithTransaction(context.Background(), func(txCtx context.Context) error { errorGroup := &errgroup.Group{} // 每个操作使用独立事务,不共享主事务上下文 errorGroup.Go(func() error { return s.addTotable1(context.Background(), *model) }) errorGroup.Go(func() error { return s.updateTable1(context.Background(), *model) }) // 其他操作... if err := errorGroup.Wait(); err != nil { transactionError = err return err } return nil }, func(trCtx context.Context) error { return transactionError })
注意:此方案下所有操作不在同一个事务中,无法保证原子性,需业务自行处理失败后的回滚逻辑。
额外优化建议
- 连接池配置:在
OpenConnection中添加连接池参数,避免连接耗尽或过期:
sqlDB.SetMaxOpenConns(20) // 最大打开连接数 sqlDB.SetMaxIdleConns(10) // 最大空闲连接数 sqlDB.SetConnMaxLifetime(time.Hour) // 连接最大存活时间
- 事务逻辑检查:你的
WithTransaction方法中,回滚原事务后开启新事务执行trFunc并直接提交,需确认此补偿逻辑是否符合业务预期。
内容的提问来源于stack exchange,提问作者david oleh hrinchenko
相关产品推荐
相关产品推荐

