gorm不原生支持读写分离,需通过dbresolver插件显式配置主从连接池;主库处理写操作及事务内所有操作,从库处理默认查询,强一致读需手动指定write策略,主从延迟须由业务层兜底。

GORM 是目前 Go 生态中最主流、最稳定的 ORM,它原生支持读写分离,不需要额外中间件或自研路由逻辑。直接用 GORM 的 WithMasterDB 和 AddSlave 就能跑通,前提是 MySQL 主从复制已就绪。
主从库连接配置必须显式分离
不能只靠一个 DSN 拼接多个地址 —— GORM 不识别这种写法,会报 invalid connection 或静默 fallback 到主库。
- 主库 DSN 单独传给
GORM.Open,用于写操作和事务起点 - 每个从库 DSN 必须调用
db.AddSlave显式注册,不能复用主库连接对象 - 从库 DSN 中的用户名/密码/地址/端口需与主库独立,尤其注意从库是否开启
read_only=ON(MySQL 层面强约束) - 若从库地址写错或不可达,
AddSlave不报错,但后续读请求会 fallback 到主库 —— 日志里看不到明显提示,得靠db.Debug().Find()观察实际执行的 IP
读操作自动路由到从库的触发条件
GORM 不是“所有 SELECT 都走从库”,它依赖上下文判断:
- 普通查询如
db.Find(&u)、db.Where("id = ?", 1).First(&u)默认走从库(前提是已注册至少一个有效从库) - 事务内任何查询(
tx := db.Begin(); tx.First(&u))强制走主库,哪怕只读 - 显式调用
db.Session(&gorm.Session{ReadOnly: true})可强制走从库,但一般不需要 - 使用
db.Unscoped()或db.Clauses(clause.Locking{Strength: "UPDATE"})等带锁语义的操作,也会退回到主库
从库负载不均或连接泄漏的常见原因
现象:部分从库 CPU 飙高、连接数暴涨,另一些几乎闲置;或运行几小时后报 dial tcp: i/o timeout。
-
AddSlave注册时没设置连接池参数,导致从库连接复用率低 —— 必须对每个从库单独调用slaveDB.DB().SetMaxIdleConns(10)和SetMaxOpenConns(50) - 没设
SetConnMaxLifetime,长连接在 MySQL 侧被 kill(如 wait_timeout=300),Go 端仍尝试复用,引发大量重连 - 从库健康检查缺失:GORM 不自动剔除宕机从库,需配合
gorm.Dialector自定义 dialer + ping 逻辑,或依赖外部探活(如 MyCat/Amoeba) - 应用启动时从库不可达,
AddSlave返回 error 被忽略,后续读请求全打到主库 —— 务必检查每个AddSlave的 error 返回
事务与跨库查询的边界必须清晰
主从延迟下,刚写入主库的数据立刻查从库可能查不到 —— 这不是 GORM 的 bug,而是 MySQL 复制机制决定的。
- 强一致性场景(如创建后立即详情页)必须用主库读:
db.WithContext(context.WithValue(ctx, gorm.ErrRecordNotFound, true)).First(&u)不行,得显式用主库实例 - 没有“强制走主库读”的全局开关,只能靠
db.Session(&gorm.Session{NewDB: true})新建会话,或维护一个只连主库的*gorm.DB副本 - 跨库 JOIN(主库表 join 从库表)不支持,SQL 会发往主库执行,从库完全不参与 —— 架构上应避免这类设计
- 从库只读权限若未在 MySQL 层设置(
GRANT SELECT ON *.* TO 'slave_user'@'%'),错误可能延迟到执行时才暴露为ERROR 1290 (HY000): The MySQL server is running with the --read-only option
实际部署时,最易被忽略的是从库连接池参数未单独配置,以及主从延迟导致的读不到新数据问题 —— 这两个点不处理,上线后监控指标会很快报警。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











