必须为每个数据源创建独立*gorm.DB实例,不可复用同一变量;需按模块命名(如userDB、logDB),各自调用gorm.Open()初始化,并单独配置连接池、DSN及驱动导入。

不能靠一个 *gorm.DB 变量来回赋值切换数据源,必须为每个业务模块声明独立变量并各自初始化。
每个模块配专属 *gorm.DB 变量,不共用、不覆盖
常见错误是定义一个全局 var db *gorm.DB,然后在不同 init 函数里反复调用 gorm.Open() 赋值——后一次会直接覆盖前一次,导致查错库、事务失效甚至 panic。
- 正确做法:按模块命名变量,例如
userDB、orderDB、logDB - 每个变量都调用一次
gorm.Open(),传入对应 DSN 和驱动(如mysql.Open(dsn)或postgres.Open(dsn)) - 别在
func init()里初始化:失败时无法返回error,日志难定位;改用显式函数,如InitUserDB() (*gorm.DB, error) - 若用依赖注入(如 fx/wire),需为每个 DB 绑定唯一类型别名,例如
type UserDB *gorm.DB,否则容器会把它们当成同一类型混用
gorm.Open() 的第一个参数必须和 DSN 类型严格匹配
传 mysql.Open(dsn) 却填 PostgreSQL 的 DSN,GORM 不会立刻报错,而是在第一次查询时卡住或返回模糊错误(如 "invalid connection" 或直接 timeout)。
- 确认已导入对应驱动:MySQL 用
import _ "gorm.io/driver/mysql",PostgreSQL 用import _ "gorm.io/driver/postgres"——下划线不能漏,否则静默失败 - DSN 中关键参数不能少:MySQL 至少含
?charset=utf8mb4&parseTime=True&loc=Local,PostgreSQL 至少含?sslmode=disable,否则某些版本握手阶段就 hang 住 - PostgreSQL 用户名/密码含特殊字符(如
@、/)时,必须用url.QueryEscape()编码,否则解析失败且错误提示毫无指向性(常报"pq: password authentication failed")
分页、条件等逻辑要解耦数据源,用 Scopes 封装
GORM 的 Page 相关操作(如 Limit/Offset)必须绑定具体 *gorm.DB 实例,无法跨数据源复用同一套逻辑。直接写成全局函数返回 []T 会丢失数据源上下文。
- Scopes 必须接收并返回
*gorm.DB,例如:func PageScope(page, pageSize int) func(*gorm.DB) *gorm.DB - 所有业务条件 scope(如
StatusScope、DateRangeScope)都应设计为纯函数,不依赖任何全局 DB 变量 - 调用时显式传入对应 DB 实例:
userDB.Scopes(StatusScope("active"), PageScope(2, 15)).Find(&users) -
PageScope内部不做数据库类型判断,因为LIMIT/OFFSET在 MySQL/PostgreSQL 中语义一致;但 Oracle 需要ROWNUM,此时应改用拦截器或显式分支
事务只作用于调用它的那个 *gorm.DB 实例
userDB.Transaction() 里调用 logDB.Create(),后者完全游离在事务外——哪怕两个库连的是同一台 PostgreSQL 实例,也做不到跨库原子性。
- 事务边界由
*gorm.DB实例决定,不是由物理数据库决定 - 如果业务强依赖跨模块一致性(如“创建订单 + 扣减库存”),必须合并到同一数据源,或改用最终一致性方案(如发 Kafka 消息)
- 不要试图在封装层统一抽象
BeginTx:MySQL 和 PostgreSQL 对隔离级别、SAVEPOINT 的支持差异很大,强行抽象反而增加出错概率
最易被忽略的一点:连接池参数(SetMaxOpenConns、SetMaxIdleConns 等)必须为每个 *gorm.DB 实例单独配置,否则某模块突发流量可能拖垮其他模块的数据库连接。











