必须为每个数据库声明独立的*gorm.DB变量,复用同一变量会导致后一次覆盖前一次、事务错乱;需分别调用gorm.Open并配置匹配驱动、DSN及连接池参数,事务仅限单实例生效。

多个*gorm.DB实例必须用不同变量名声明
全局复用同一个 *gorm.DB 变量去接多个 gorm.Open() 返回值,是导致跨库操作错乱的根源。比如写 var db *gorm.DB,然后先后赋值为 MySQL 和 PostgreSQL 的实例,后一次会直接覆盖前一次——所有模型操作都跑到最后一个库上,事务、日志器、回调全错位。
正确做法是为每个库定义专属变量:
-
var userDB *gorm.DB(主业务库) -
var logDB *gorm.DB(日志库) -
var reportDB *gorm.DB(报表库)
每个变量都由独立的 gorm.Open() 创建,并传入对应驱动和 DSN。GORM v2 不共享连接池或配置,变量名隔离就是最基础的安全边界。
DSN 和驱动不匹配会导致静默失败或卡死
用 mysql.Open(dsn) 却传了 PostgreSQL 的 DSN(如 postgres://user:pass@localhost:5432/db),GORM 不会在 Open() 时报错,而是在第一次查询时 hang 住,或返回模糊的 invalid connection。
检查要点:
- 确认已导入对应驱动:
import _ "gorm.io/driver/mysql"或import _ "gorm.io/driver/postgres"—— 下划线不能少,否则驱动不注册 - MySQL DSN 必须带
?charset=utf8mb4&parseTime=True&loc=Local,漏掉parseTime会导致time.Time字段解析失败 - PostgreSQL DSN 中用户名/密码含
@或/时,必须用url.QueryEscape()编码,否则解析出错且错误提示完全误导(例如显示pq: password authentication failed却密码正确)
连接池参数必须显式设置,不能依赖默认值
默认 MaxOpenConns=0(无上限)、MaxIdleConns=2,压测时立刻出现 dial tcp: connect: cannot assign requested address 或大量超时。这不是 GORM 的问题,而是底层 sql.DB 连接池失控。
必须在 gorm.Open() 后立即调用:
db.DB().SetMaxOpenConns(50)db.DB().SetMaxIdleConns(10)db.DB().SetConnMaxLifetime(30 * time.Minute)db.DB().SetConnMaxIdleTime(10 * time.Minute)
配对设置生命周期很重要:不设 ConnMaxLifetime,MySQL 的 wait_timeout 会断连且 GORM 不重连;不设 ConnMaxIdleTime,空闲连接长期不释放,可能耗尽系统 fd。
事务无法跨*gorm.DB实例生效
写 userDB.Transaction(func(tx *gorm.DB) error { userDB.Create(); logDB.Create() }) 是无效的:只有 userDB.Create() 在事务内,logDB.Create() 完全游离在外,甚至可能连到另一台物理机器。
真实约束:
-
Transaction()只作用于调用它的那个*gorm.DB实例 - 跨库原子性不存在于 GORM 层,必须靠业务补偿(例如先写主库成功,再发消息异步写日志库;失败则触发告警 + 人工核对)
- 若需单库内连接复用(如避免事务中新建连接),可用
userDB.Session(&gorm.Session{NewDB: true}).Begin(),但这不解决跨库问题
最容易被忽略的是:哪怕两个库连的是同一台 PostgreSQL 实例,GORM 也完全无法协调它们的事务边界。这点在设计分库策略时必须前置确认,不能等上线后才发现数据不一致。











