必须显式在gorm.open之后调用db.use(dbresolver.register()),否则插件不生效、读写分离失效;主从库需各自独立sql.open并调优连接池参数,路由依据方法语义而非sql解析,强一致读须显式标注write。

dbresolver.Register 必须在 gorm.Open 之后调用
插件没注册成功,读写分离就根本不存在——db.Use(dbresolver.Register()) 放在 gorm.Open() 前,dbresolver 完全不会生效。GORM v2 要求插件必须绑定到已初始化的 *gorm.DB 实例上,否则所有 Find、First 都静默 fallback 到主库,日志里不报错也不提示。
- 正确顺序:先
db, err := gorm.Open(mysql.Open(dsnMaster), &gorm.Config{}),再立刻db.Use(dbresolver.Register(...)) - 主库 DSN 在
gorm.Open()里只是占位作用,实际路由由dbresolver.Config中的Sources和Replicas决定 - 漏掉
.Use()这一步,整个配置就是“假分离”,压测时才发现读请求全打主库
主库和每个从库必须各自 sql.Open,不能复用连接池
共用一个 *sql.DB 或浅拷贝配置指针,会导致连接池参数错乱、健康状态无法隔离、故障时互相拖累。MySQL 主库和从库是负载特征完全不同的服务端点,连接池参数必须独立调优。
- 主库建议:
SetMaxOpenConns(50)、SetWriteTimeout(3 * time.Second)、SetConnMaxLifetime(5 * time.Minute) - 每个从库单独
sql.Open,建议:SetMaxOpenConns(100)、SetReadTimeout(10 * time.Second)、SetConnMaxLifetime(10 * time.Minute) - 注册前务必对每个从库调用
slaveDB.Ping(),失败要记录告警——AddSlave或Replicas中地址不可达时不会报错,但后续读请求会静默 fallback 到主库
路由靠方法语义,不是 SQL 解析
GORM 不解析你写的 "SELECT ... FOR UPDATE" 或 "INSERT ... SELECT",它只看调用方式。默认规则简单粗暴,但容易踩坑。
-
Create/Save/Update/Delete/Begin→ 强制走Sources(主库) -
Find/First/Where().Scan()/Model().Count()→ 默认走Replicas(从库) -
Raw("SELECT * FROM users").Scan()默认走从库;但Raw("INSERT INTO ...")或Raw("SELECT ... FOR UPDATE")会直接在从库报错ERROR 1290 (HY000): The MySQL server is running with the --read-only option - 事务内所有操作(包括
tx.First())强制走主库,哪怕只是查刚写入的数据
强一致读必须手动切主库,不能依赖“刚写完就查”
MySQL 主从延迟是物理现实,GORM 不重试、不等待、不补偿。所谓“查不到最新数据”,99% 是业务没控制好时序,而不是框架问题。
- 刚
Create完立刻First,大概率读到旧数据——因为从库还没同步完 - 正确做法:
db.Clauses(dbresolver.Write).Where("id = ?", id).First(&u)或db.Session(&gorm.Session{Write: true}).First(&u) -
Session必须在链式调用最开头,Where().Session().First()无效 - 不要指望 GORM 自动感知
Seconds_Behind_Master,它连SHOW SLAVE STATUS都不执行;延迟兜底必须由业务层自己轮询、过滤、降级











