gorm不支持单实例自动切换异构库,必须为每个数据库独立初始化*gorm.db实例;需分别导入对应驱动、传入匹配dialector、配置专属连接池及dsn关键参数,并通过类型别名或结构体封装避免混淆。

不能靠一个 *gorm.DB 实例“自动切换”多个数据库,GORM 本身不支持单实例路由到异构库;必须为每个数据库单独初始化独立的 *gorm.DB 实例,并显式管理其生命周期。
每个数据库都要调用一次 gorm.Open()
复用同一个 gorm.Open() 调用结果去连 MySQL 和 PostgreSQL,必然失败——后一次会覆盖前一次变量,或者因驱动未注册而报 driver: unknown driver "postgres"。GORM v2 的 gorm.Open() 第一个参数是 Dialector,不是字符串,类型不匹配会导致静默连接失败或运行时报错。
- MySQL 必须导入
_ "gorm.io/driver/mysql",然后传mysql.Open(dsn) - PostgreSQL 必须导入
_ "gorm.io/driver/postgres",然后传postgres.Open(dsn) - DSN 中关键参数不能省:MySQL 至少带
?charset=utf8mb4&parseTime=True&loc=Local,PostgreSQL 必须含?sslmode=disable(否则某些环境握手卡住) - 用户名/密码含
@、/等特殊字符时,需先用url.QueryEscape()编码,否则解析 DSN 失败且错误提示毫无指向性
别把多个 *gorm.DB 塞进全局 map 裸用
直接用 var dbs = map[string]*gorm.DB{} 存多个实例,在并发写入(如热重载配置时更新)下会 panic——map 非并发安全。更隐蔽的问题是:没有封装就无法统一做健康检查、连接池重置或日志标记。
- 推荐定义结构体字段封装,例如:
type DBManager struct { UserDB *gorm.DB; LogDB *gorm.DB } - 如果用依赖注入(如 Wire/fx),给每个库绑定唯一类型别名,比如
type UserDB *gorm.DB,避免容器误认为是同一类型 - 初始化函数必须返回
error,不要放在func init()里——失败无法反馈,日志无上下文,线上排障极难定位
db.Table().Where().Find() 跨库混用会语法报错
看似只是换了个表名,但 db 实例绑定了方言(dialect)、类型映射、标识符规则。MySQL 用反引号 `user`,PostgreSQL 用双引号 "user",直接拿 MySQL 的查询逻辑跑在 PG 实例上,立刻报 syntax error at or near "user"。
-
db.Table("users").Select("name")不会自动适配目标库的字段引用方式 - 即使模型结构体字段名一致,底层类型映射也可能不同:MySQL 的
TINYINT(1)映射为 bool,PG 的BOOLEAN才是 bool,混用易导致 Scan 失败 - 事务也只作用于当前
*gorm.DB实例,userDB.Transaction()里调logDB.Create()完全游离在外,不是 bug,是设计如此
连接池和超时参数必须按库单独设
不同数据库负载特征差异大:用户库读多写少,日志库写高频低一致性要求。共用一套连接池参数(如都设 SetMaxOpenConns(10))容易导致某库连接耗尽,另一库空闲。
- 每个
*gorm.DB初始化后必须立即调用:db.SetMaxOpenConns(10)、db.SetMaxIdleConns(5)、db.SetConnMaxLifetime(60 * time.Second) - PostgreSQL DSN 中的
sslmode=disable是硬性要求,漏掉会在某些版本上 hang 住,而不是报错 - 国产库(如达梦、人大金仓)需自定义
Dialector,但连接池设置逻辑不变,仍要单独调用Set*方法
最常被忽略的是:事务跨库从来就不可行,不是配置问题,是分布式事务范畴;而 DSN 中缺失 loc=Local 或 sslmode=disable 这类参数,不会在 gorm.Open() 时报错,而是在第一次查询时才暴露,排查成本极高。











