多数据源需为每个库创建独立* gorm.db实例,禁止共享变量或连接池,驱动导入、dsn格式、事务隔离、连接池配置及监控打标均须严格区分。

多数据源不是“配多个 DSN 就完事”,而是每个库必须有独立的 *gorm.DB 实例,且不能共享、不能混用。
gorm.Open 调用必须一对一绑定驱动和 DSN
常见错误是把 PostgreSQL 的 DSN 传给 mysql.Open(),或者漏掉驱动导入。GORM 不会在 Open 时校验,而是在第一次查询时卡住或报 "invalid connection"。
- 确认已导入对应驱动:
import _ "gorm.io/driver/mysql"或import _ "gorm.io/driver/postgres"—— 下划线不能少,否则静默失败 - MySQL DSN 必须带
?charset=utf8mb4&parseTime=True&loc=Local,否则某些版本握手超时 - PostgreSQL DSN 中用户名/密码含
@、/等字符时,必须用url.QueryEscape编码,否则报"pq: password authentication failed"却查不出原因 - 别在
init()里初始化多个 DB:错误无法返回,panic 难定位;改用显式函数,如func NewUserDB(cfg Config) (*gorm.DB, error)
变量命名必须区分不同库实例
全局变量 var db *gorm.DB 是多库场景下最典型的误用。一旦被覆盖(比如第二个 gorm.Open 赋值给同一个变量),后续所有操作都指向最后一个库。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 为每个库声明独立变量:
var userDB, logDB, cacheDB *gorm.DB - 如果用依赖注入(如 Wire / fx),需定义包装类型避免混淆:
type UserDB *gorm.DB、type LogDB *gorm.DB,再分别绑定 - 禁止在中间件或 handler 里通过参数传递“通用 db”变量——它没有上下文语义,极易传错实例
事务不跨库,也不能靠 Session 模拟
userDB.Transaction() 只对 userDB 生效;里面调用 logDB.Create() 完全游离在事务外,哪怕两个库连的是同一台 PostgreSQL 实例。
- GORM 无跨库事务能力,这不是配置问题,是设计限制
-
db.Session(&gorm.Session{NewDB: true})只控制单库内连接复用,跟其他 DB 实例无关 - 若需强一致性,必须业务层补偿:先写主库,成功后发消息/调异步任务写日志库,失败走告警+人工核对
-
db.WithContext(ctx).Transaction()中的ctxtimeout 仅约束本库事务,不影响其他 DB 实例的执行时长
连接池必须各自独立配置
每个 *gorm.DB 默认自带独立连接池,但有人会误调 db.Config.ConnPool 或直接改底层 sql.DB,结果多个库共用同一池子——一个库慢查询拖垮全部。
- 连接池配置应在
gorm.Open后立即设置,且只作用于当前实例:userDB.Config.ConnPool = &sql.DB{...}不合法;正确做法是sqlDB, _ := userDB.DB(); sqlDB.SetMaxOpenConns(50) - 别复用
sql.DB:从userDB.DB()取出的*sql.DB仅属于该 GORM 实例,不能塞给logDB - 监控时注意区分:每个库的连接数、慢查询、panic 日志都要按实例打标,否则聚合后无法归因
真正难的不是写几个 gorm.Open,而是确保它们在整个生命周期里不被意外共享、不被中间件透传错、不被日志或 metric 混淆来源——这些细节在压测或上线后才暴露,但修复成本远高于初始化时多写两行类型定义。










