每个gorm.DB实例对应独立sql.DB,必须各自调用SetMaxOpenConns等方法配置连接池,漏配会导致某库连接耗尽而其他库空闲;需按库单独压测调参,并注意DSN参数完整性与驱动导入正确性。

多个 *gorm.DB 实例必须各自调用 SetMaxOpenConns 等连接池方法
很多人以为配置一次全局连接池就够了,其实每个 *gorm.DB 对应独立的底层 *sql.DB,连接池参数不共享。漏配会导致某个库连接耗尽(比如日志库突增写入),而主库连接还很空闲。
正确做法是:每次 gorm.Open() 后立刻获取原生连接池并设置:
userDB, err := gorm.Open(mysql.Open(userDSN), &gorm.Config{})sqlDB, _ := userDB.DB()sqlDB.SetMaxOpenConns(30)sqlDB.SetMaxIdleConns(10)sqlDB.SetConnMaxLifetime(60 * time.Second)sqlDB.SetConnMaxIdleTime(30 * time.Second)
PostgreSQL 和 MySQL 对 max_connections 敏感度不同,达梦等国产库默认上限更低(如仅 100),必须按库单独压测后调整,不能照搬同一套数值。
DSN 中时区、字符集、SSL 参数缺失导致连接 hang 住或解析失败
MySQL DSN 缺少 ?charset=utf8mb4&parseTime=True&loc=Local,首次查询可能 panic 或时间字段错乱;PostgreSQL DSN 忘加 ?sslmode=disable,在某些版本会卡在 TLS 握手阶段,错误提示却是 "dial tcp: i/o timeout",毫无指向性。
更隐蔽的是 PostgreSQL 用户名/密码含 @ 或 / 时未编码:
- 错误写法:
postgres://user@name:pass/word@localhost:5432/db - 正确写法:用
url.QueryEscape()编码后拼接,否则报"pq: password authentication failed",但实际是 URL 解析把@当作了 host 分隔符
事务只绑定单个 *gorm.DB 实例,跨库调用 Transaction() 无效
常见误写:userDB.Transaction(func(tx *gorm.DB) error { userDB.Create(...); logDB.Create(...) }) —— 这里 logDB.Create() 完全游离在事务外,即使两个库连同一台 PostgreSQL 实例,也无法回滚。
GORM 的事务机制只控制当前 *sql.Tx 生命周期,不跨 *sql.DB 协调。硬要“伪事务”也只限本库内隔离:
- 用
userDB.Session(&gorm.Session{NewDB: true}).Begin()控制子连接,但对logDB依然无效 - 跨库一致性必须业务层兜底:先写主库成功 → 发消息触发日志写入 → 失败走补偿任务,别指望 ORM 解决
驱动导入遗漏或命名策略未按库单独传参
忘记下划线导入驱动是静默失败的典型:import _ "gorm.io/driver/mysql" 少了 _,运行时报 "driver: unknown driver \"mysql\" (forgotten import?)",但编译不报错。
另一个常被忽略的是 NamingStrategy:主库用 user_profiles(复数+下划线),日志库用 UserActionLog(驼峰),不能共用一个 gorm.Config 初始化。必须显式传入:
userDB := gorm.Open(mysql.Open(dsn), &gorm.Config{NamingStrategy: schema.NamingStrategy{SingularTable: true}})logDB := gorm.Open(postgres.Open(dsn), &gorm.Config{NamingStrategy: schema.NamingStrategy{NoLowerCase: true}})
如果结构体定义了 TableName() 方法,它优先级高于 NamingStrategy,但若同时写了 gorm:"table_name:xxx" 标签,则标签会覆盖方法 —— 这个覆盖链容易在多库场景下引发表名错配。


















