必须显式注册dbresolver插件且主从连接池独立初始化,否则读请求走主库或写操作误发从库报错error 1290;dbresolver.register须在gorm.open之后调用,主从需各自sql.open并配置不同连接池参数,强一致读和带锁查询必须显式标注write。

必须显式注册 dbresolver 插件,且主从连接池要各自独立初始化,否则所有读请求仍走主库,或写操作误发到只读从库直接报错 ERROR 1290 (HY000): The MySQL server is running with the --read-only option。
dbresolver.Register 必须在 gorm.Open 之后调用
很多人把 db.Use(dbresolver.Register()) 放在 gorm.Open() 前,结果插件根本没绑定成功。GORM 要求插件必须作用于已初始化的 *gorm.DB 实例。
- 第一步:用主库 DSN 初始化基础 DB(它只是占位,不承担实际路由)
db, err := gorm.Open(mysql.Open(dsnMaster), &gorm.Config{}) - 第二步:立刻调用
Use注册插件,传入含Sources和Replicas的配置db.Use(dbresolver.Register(dbresolver.Config{ Sources: []gorm.Dialector{mysql.Open(dsnMaster)}, Replicas: []gorm.Dialector{mysql.Open(dsnSlave1), mysql.Open(dsnSlave2)}, })) - 漏掉第二步,
Find、First等方法全部 fallback 到主库 —— 日志里无提示,“分离”是假象
主库和从库必须用两个独立的 *sql.DB 实例
共用一个 *sql.DB 或浅拷贝配置指针,会导致连接池参数错乱、健康状态无法隔离、故障时互相拖累。
- 主库单独
sql.Open,设SetMaxOpenConns(50)、SetWriteTimeout(3 * time.Second)、SetConnMaxLifetime(5 * time.Minute),初始化后立刻db.Ping(),失败直接panic - 每个从库也必须单独
sql.Open,不能复用主库对象;建议SetMaxOpenConns(100)、SetReadTimeout(10 * time.Second)、SetConnMaxLifetime(10 * time.Minute) -
AddSlave或Replicas中地址不可达时不会报错,但后续读请求会静默 fallback 到主库 —— 必须检查返回的error并记录告警
路由靠方法语义,不是 SQL 解析
GORM 不解析你写的 "SELECT ... FOR UPDATE" 或 "INSERT ... SELECT",它只看调用方式。默认规则简单粗暴:
-
Create/Save/Update/Delete/Begin→ 强制走Sources(主库) -
Find/First/Where().Scan()→ 默认走Replicas(从库) -
Raw("SELECT * FROM users").Scan()默认走从库;但如果里面实际是INSERT,就会在从库直接报错 - 事务内所有操作(哪怕只是
tx.QueryRow())都绑定主库连接,没法中途切到从库 -
Where().Session().First()中Session放后面就可能失效 —— 必须放在链式调用最开头
强一致读和带锁查询必须显式指定主库
刚 Create 完立刻 First,大概率读不到最新数据,因为从库还没同步完。这不是 GORM 的 bug,而是 MySQL 复制机制决定的。
- 强一致读(如支付扣款前查余额)必须加:
db.Clauses(dbresolver.Write).Where("id = ?", 1).First(&u) - 带锁查询(
FOR UPDATE、LOCK IN SHARE MODE)必须加:db.Session(&gorm.Session{Write: true}).Where("id = ?", 1).First(&u) -
Raw("SELECT * FROM users WHERE id = ? FOR UPDATE")默认发到从库,直接触发ERROR 1290—— 必须加.Clauses(dbresolver.Write) - 从库延迟高时,GORM 不会自动降级或重试;业务层得自己轮询
SHOW SLAVE STATUS,并调用Resolver.ReplaceReplicas()动态剔除延迟 >10s 的节点
真正难的不是配通读写分离,而是想清楚哪些读必须强一致、哪些可以接受延迟,并在代码里用对 Clauses 或 Session —— 写错位置、漏掉显式标注,都会让分离逻辑失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











