dbresolver.register必须在gorm.open之后调用,否则插件无法绑定到已初始化的*gorm.db实例;主从连接池须独立sql.open并分别配置参数,replicas不可复用主库连接,否则导致读请求静默fallback至主库。

dbresolver.Register 必须在 gorm.Open 之后调用
很多人把 db.Use(dbresolver.Register()) 放在 gorm.Open() 前,结果插件根本没绑定成功。GORM 要求插件必须作用于已初始化的 *gorm.DB 实例——它只认“活”的 DB 对象,不认构造参数。
正确顺序只有两步:
- 先用主库 DSN(哪怕只是占位)调用
gorm.Open(mysql.Open(dsnMaster), &gorm.Config{}),得到基础db - 紧接着立刻调用
db.Use(dbresolver.Register(...)),传入含Replicas的完整配置
漏掉第二步,所有 Find、First 都静默 fallback 到主库,日志里无任何提示,“只读副本”形同虚设。
Replicas 必须是独立 sql.DB 实例,不能复用主库连接池
每个从库必须单独调用 sql.Open 初始化,不能共用主库的 *sql.DB 或浅拷贝配置指针。否则连接池参数(如 SetMaxOpenConns、SetReadTimeout)会错乱,健康状态无法隔离,一个从库挂掉可能拖垮整个读链路。
推荐配置:
- 主库:设
SetMaxOpenConns(50)、SetWriteTimeout(3 * time.Second)、SetConnMaxLifetime(5 * time.Minute) - 每个从库:设
SetMaxOpenConns(100)、SetReadTimeout(10 * time.Second)、SetConnMaxLifetime(10 * time.Minute) - 每个
sql.Open后必须db.Ping(),失败直接panic,不能靠后续请求暴露问题
Raw 查询默认走从库,但 SQL 类型不校验
db.Raw("SELECT * FROM users").Scan(&u) 默认发往 Replicas,但 GORM 不解析 SQL 文本。如果里面实际是 INSERT ... SELECT 或带 FOR UPDATE,从库会直接报错 ERROR 1290 (HY000): The MySQL server is running with the --read-only option。
安全做法:
- 纯查询才走
Raw+ 从库 - 含写语义或加锁的语句,必须显式标注:
db.Clauses(dbresolver.Write).Raw(...) - 事务内所有操作(包括
Raw)自动绑定主库,无需额外处理
从库地址不可达时不会报错,但读请求静默切主库
dbresolver.Register 中 Replicas: []gorm.Dialector{mysql.Open(dsnSlave)} 如果 dsnSlave 网络不通或权限错误,注册过程不返回 error,也不会 panic。后续读请求会自动 fallback 到主库——流量悄悄回流,监控难发现。
必须手动检查:
- 对每个从库 DSN 单独
sql.Open并Ping() - 记录告警日志,比如
WARN: slave db at 10.0.1.5:3306 unreachable, falling back to master - 延迟高(如
Seconds_Behind_Master > 10)需业务层轮询判断,GORM 本身不感知复制延迟
真正麻烦的不是配不配得上,而是配完了你以为它在读从库,其实它早就在默默打主库了。连接池隔离、注册时机、fallback 静默性,三者任何一个出错,读写分离就变成“心理安慰”。











