GORM Postgres事务中并行查询出现driver: bad connection问题求助
在GORM事务中并行执行查询出现「driver: bad connection」错误的问题排查与解决
问题描述
我在使用GORM操作PostgreSQL时遇到一个问题:当在事务中并行执行多条查询时,会抛出「driver: bad connection」错误。如果注释掉事务开启(tx := db.Begin())和提交(tx.Commit())的相关代码,这个问题就不会复现。我不清楚问题原因,希望得到分析和解决建议。
复现代码
package main import ( "github.com/avast/retry-go" "github.com/sirupsen/logrus" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" "os" "strconv" "time" ) var ( db *gorm.DB ) func NewGorm(databaseURL string) (*gorm.DB, error) { db, err := gorm.Open( postgres.New( postgres.Config{ DSN: databaseURL, PreferSimpleProtocol: true, // disables implicit prepared statement usage }, ), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }, ) if err != nil { return nil, err } return db, nil } func Init() { databaseURL := os.Getenv("DATABASE_URL") logrus.WithField("databaseURL", databaseURL).Info("Database connection") if err := retry.Do( func() error { var connectionErr error db, connectionErr = NewGorm(databaseURL) if connectionErr != nil { logrus.Warning("retrying database") return connectionErr } return nil }, ); err != nil { logrus.WithError(err).Panic("database") } if debug, err := strconv.ParseBool(os.Getenv("DEBUG")); debug && err == nil { db = db.Debug() } else { logrus.Warning(err) } } func select_from_db(tx *gorm.DB) { tx.Table("table_name").Find(&[]models.A{}) tx.Table("table_name").Find(&[]models.A{}) tx.Table("table_name").Find(&[]models.A{}) tx.Table("table_name").Find(&[]models.A{}) } func main() { logrus.SetLevel(logrus.TraceLevel) Init() tx := db.Begin() go select_from_db(tx) go select_from_db(tx) go select_from_db(tx) go select_from_db(tx) go select_from_db(tx) time.Sleep(3 * time.Second) tx.Commit() }
运行错误信息
[0.222ms] [rows:0] SELECT * FROM "table_name" 2023/06/15 13:14:51 main.go:72 driver: bad connection
问题原因分析
- GORM的事务对象(
*gorm.DB)不是线程安全的:事务绑定了单个数据库连接,多个goroutine并行操作时会同时读写这个连接的状态,导致连接内部逻辑混乱,最终触发「bad connection」错误。 - 非事务场景下,GORM会自动从连接池为每个操作分配独立连接,每个goroutine使用各自的连接,不会出现竞争冲突;但事务场景下所有操作复用同一个连接,并行操作必然导致资源竞争。
解决办法
方案1:事务内操作改为串行执行
如果业务允许,将并行调用改为串行,确保同一事务内的操作按顺序执行,避免连接竞争:
func main() { logrus.SetLevel(logrus.TraceLevel) Init() tx := db.Begin() select_from_db(tx) select_from_db(tx) select_from_db(tx) select_from_db(tx) select_from_db(tx) tx.Commit() }
方案2:为每个goroutine创建独立事务
如果必须并行处理,让每个goroutine开启自己的独立事务,操作完成后各自提交(注意这种方式下多个事务是独立的,无法保证全局原子性):
func select_from_db(db *gorm.DB) { tx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } else { tx.Commit() } }() tx.Table("table_name").Find(&[]models.A{}) tx.Table("table_name").Find(&[]models.A{}) tx.Table("table_name").Find(&[]models.A{}) tx.Table("table_name").Find(&[]models.A{}) } func main() { logrus.SetLevel(logrus.TraceLevel) Init() go select_from_db(db) go select_from_db(db) go select_from_db(db) go select_from_db(db) go select_from_db(db) time.Sleep(3 * time.Second) }
方案3:放弃事务,使用连接池处理并行操作
如果业务不需要事务的原子性保证,直接使用全局db对象执行查询,GORM会自动从连接池为每个goroutine分配独立连接,避免竞争:
func select_from_db(db *gorm.DB) { db.Table("table_name").Find(&[]models.A{}) db.Table("table_name").Find(&[]models.A{}) db.Table("table_name").Find(&[]models.A{}) db.Table("table_name").Find(&[]models.A{}) } func main() { logrus.SetLevel(logrus.TraceLevel) Init() go select_from_db(db) go select_from_db(db) go select_from_db(db) go select_from_db(db) go select_from_db(db) time.Sleep(3 * time.Second) }
内容的提问来源于stack exchange,提问作者Omri. B
相关产品推荐
相关产品推荐

