gorm读写分离需显式注册dbresolver插件且严格遵循初始化顺序,否则读操作仍走主库或写操作误发从库报错;方法按名称硬路由,raw sql不解析语义易致从库执行for update失败;延迟与故障需业务层自行处理。

GORM 的读写分离不是“配完就自动生效”,必须显式注册 dbresolver 插件,且主从连接池初始化顺序、方法语义路由规则、延迟兜底逻辑全部要手动对齐,否则所有读操作仍走主库,或写操作误发到从库报 ERROR: cannot execute INSERT in a read-only transaction。
dbresolver.Register 必须在 gorm.Open 之后调用
很多人把 db.Use(dbresolver.Register(...)) 放在 gorm.Open 前,结果插件根本没绑定成功——dbresolver.Register 只能作用于已初始化的 *gorm.DB 实例。正确顺序是:
- 先用
gorm.Open(dialector, &gorm.Config{})初始化基础*gorm.DB(通常传主库 DSN,但它此时只是占位,不承担实际路由) - 紧接着调用
db.Use(dbresolver.Register(...)),传入含Sources(主库)和Replicas(从库列表)的配置 - 漏掉第二步,
Find、First等方法依然全部走主库,所谓“分离”只是假象
Create/Update 默认走主库,但 Raw("SELECT ... FOR UPDATE") 会发到从库报错
GORM 不解析 SQL 文本,只按方法名硬路由:Create、Save、Update、Delete、Transaction 强制走 Sources;Find、First、Count 默认走 Replicas。但问题在于:
-
Raw("SELECT * FROM users WHERE id = ? FOR UPDATE")被当普通读操作,发到从库直接触发ERROR 1290 (HY000): The MySQL server is running with the --read-only option -
Where().Select().Scan()链式调用中若混入Session顺序错,策略可能失效 - 需要强一致读(如刚
Create完立刻First),必须显式加db.Session(&gorm.Session{Write: true})
从库延迟 >5s 时 GORM 不会自动降级,业务层必须自己查 Seconds_Behind_Master
dbresolver 完全不感知复制延迟,也不查 SHOW SLAVE STATUS。它把 Replicas 当静态列表,节点宕机后不会自动剔除。这意味着:
- 从库
Seconds_Behind_Master = 12时,Find仍照常转发,导致“刚提交就查不到” - 要实现延迟感知,得额外维护从库状态 map,定期轮询各节点并调用
Resolver.ReplaceReplicas()动态刷新 - 事务内所有操作(包括
SELECT)都绑定主库连接,不能靠“从库快就切过去”——事务一致性优先级高于负载分担
每个 *sql.DB 连接池参数必须单独调优,不能复用 sql.Open 返回值
主库和从库是负载特征完全不同的服务端点,共用连接池配置会引发故障:
- 主库:设
SetMaxOpenConns(30)、SetWriteTimeout(2 * time.Second),初始化后立刻Ping(),失败直接 panic - 从库:设
SetMaxOpenConns(120)、SetReadTimeout(8 * time.Second),首次读前异步Ping(),超时标记isHealthy = false - 绝不能
slaveDB := masterDB浅拷贝指针——*sql.DB是有状态对象,复制无意义 - 每个实例都需单独
defer db.Close(),HTTP 服务建议在 shutdown 阶段统一关闭
真正麻烦的不是配置步骤,而是所有“自动”预期都要被推翻:GORM 不识别 SQL 语义、不感知延迟、不自动故障剔除、不跨库事务——这些全是业务代码要填的坑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











