dbresolver.Register必须在gorm.Open之后调用,否则读写分离失效;主从库需独立sql.Open并调优连接池,路由按方法语义而非SQL解析,强一致读须显式标注Write。

dbresolver.Register 必须在 gorm.Open 之后调用
插件没生效是读写分离失败最常见的原因。GORM 不会在 gorm.Open 前绑定插件,db.Use(dbresolver.Register()) 必须紧跟在 gorm.Open 初始化之后,否则所有 Find、First 请求仍走主库,且无任何错误提示。
正确顺序只有这一种:
- 先调
gorm.Open(mysql.Open(mainDSN), &gorm.Config{})得到基础*gorm.DB(此时 DSN 仅占位,不决定路由) - 紧接着调
db.Use(dbresolver.Register(...)),传入含Sources和Replicas的完整配置 - 漏掉第二步,等于没配读写分离
主库和从库必须是两个独立的 *sql.DB 实例
共用一个 *sql.DB 或浅拷贝连接池指针,会导致连接池参数错乱、健康状态无法隔离、故障互相拖累。GORM 的 dbresolver 路由本质是两套物理连接池,不是语义转发代理。
- 主库:单独
sql.Open(),设SetMaxOpenConns(20)、SetWriteTimeout(3 * time.Second)、SetConnMaxLifetime(5 * time.Minute) - 每个从库:各自
sql.Open(),不能复用主库对象;设SetMaxOpenConns(100)、SetReadTimeout(10 * time.Second)、SetConnMaxLifetime(10 * time.Minute) - 注册从库时,
db.AddSlave()返回error必须检查——失败不会 panic,但后续读请求会静默 fallback 到主库
Raw 查询默认走从库,但 SQL 语义不被解析
db.Raw("SELECT * FROM users FOR UPDATE").Scan() 看似是 SELECT,实际带写锁,发到只读从库直接报 ERROR 1290 (HY000): The MySQL server is running with the --read-only option。GORM 不解析 SQL 文本,只按方法名硬路由。
-
Create/Update/Delete/Begin强制走主库 -
Find/First/Count默认走从库 -
Raw默认走从库,含FOR UPDATE、INSERT ... SELECT、WITH RECURSIVE等写语义时必须显式切主:db.Clauses(dbresolver.Write).Raw(...).Scan() - 事务内所有操作(包括
tx.QueryRow())自动绑定主库连接,无法中途切从库
强一致读和锁查询必须手动指定 Write 意图
刚 Create 完立刻 First 查,或需要 SELECT ... FOR UPDATE,都不能依赖默认路由。从库延迟是客观存在的,GORM 不感知 Seconds_Behind_Master,也不自动剔除宕机节点。
- 强一致读:用
db.Clauses(dbresolver.Write).First(&u)或db.Session(&gorm.Session{Write: true}).First(&u) - 带锁查询:必须加
db.Session(&gorm.Session{Write: true}),否则从库拒绝执行 - 延迟兜底要业务层自己做:定期轮询各从库
SHOW SLAVE STATUS,维护可用节点列表,必要时调用Resolver.ReplaceReplicas()动态刷新
最易被忽略的是:每个从库的 *sql.DB 都必须单独 Ping() 校验连通性,不能只靠 dbresolver.Register 的返回值判断——注册成功不代表能连上,第一次查询失败才暴露问题,那时流量已经压过去了。











