配了dbresolver却没生效,90%因未调db.Use(dbresolver.Register())或Sources/Replicas写反:必须gorm.Open后立即注册插件,Sources仅主库、Replicas仅从库,写操作打从库会报read-only错误。

配了 dbresolver 却没生效,90% 是因为没调 db.Use(dbresolver.Register()),或者 Sources 和 Replicas 写反了——写操作打到从库会直接报 ERROR: cannot execute INSERT in a read-only transaction。
必须在 gorm.Open 之后立即注册 dbresolver 插件
很多人把 db.Use(dbresolver.Register(...)) 放在 gorm.Open 前,或包在某个 init 函数里延迟执行,结果插件根本没绑定到 DB 实例上。GORM 要求插件必须注册到已初始化的 *gorm.DB 上,否则所有路由逻辑都不触发。
-
db, err := gorm.Open(mysql.Open(dsnMaster), &gorm.Config{})是第一步,且dsnMaster仅用于初始化基础连接(此时它还不承担实际路由) - 紧接着必须调用
db.Use(dbresolver.Register(...)),否则Find、First等方法仍全部走主库 - 如果漏掉这步,日志里不会报错,但
db.Debug().Find(&u)会显示 SQL 全发到了主库 IP,属于“假分离”
Sources 和 Replicas 必须严格按语义填写,不能颠倒
Sources 是写入口,只放主库;Replicas 是读副本,只放从库。写反会导致 Create、Update 直接打到只读库,MySQL 立即返回错误。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
-
Sources: []gorm.Dialector{mysql.Open(dsnMaster)}—— 只能是主库 DSN,且建议只设一个 -
Replicas: []gorm.Dialector{mysql.Open(dsnSlave1), mysql.Open(dsnSlave2)}—— 每个从库 DSN 必须单独mysql.Open(),不能复用同一个连接对象 - 从库 DSN 中必须含
read_only=1或已在 MySQL 层开启read_only=ON,否则SELECT FOR UPDATE在从库执行会报ERROR 1290 (HY000) - 每个
AddSlave或mysql.Open()调用后必须检查error,注册失败时 GORM 不 panic,后续读请求全 fallback 到主库
读写路由不看 SQL 文本,只看方法签名和上下文
GORM 不解析 "SELECT" 或 "INSERT" 字符串,而是硬编码规则:写操作强制走 Sources,普通读默认走 Replicas,事务内全部走主库。
- 写操作:
Create、Save、Update、Delete、Exec、Transaction→ 强制走Sources - 读操作:
Find、First、Take、Count、Scan→ 默认走Replicas(前提是至少一个从库注册成功且健康) - 事务内:
tx := db.Begin()后所有tx.Find()、tx.First()都走主库,与 SQL 内容无关 -
Raw("SELECT ...")默认也走从库;但Raw("INSERT ...")会失败,必须显式加.Clauses(dbresolver.Write)
强一致读必须手动切主库,GORM 不自动补偿延迟
刚 Create 完立刻 First,大概率读不到最新数据——这不是 GORM 的 bug,而是 MySQL 主从复制的物理延迟。GORM 不感知 Seconds_Behind_Master,也不会重试或降级。
- 强一致读(如支付前查余额、创建后立即详情页)必须显式指定:
db.Clauses(dbresolver.Write).Where("id = ?", id).First(&u) - 或用 session:
db.Session(&gorm.Session{Write: true}).First(&u),注意必须放在链式调用最开头,Where().Session().First()无效 -
db.Unscoped()、db.Clauses(clause.Locking{Strength: "UPDATE"})这类带锁语义的操作也会退回到主库 - 从库连接池建议设更高
SetMaxOpenConns(比如 200),主库保守些(比如 50),避免写操作被连接耗尽阻塞
真正难的不是让 dbresolver 跑起来,而是想清楚哪些读必须强一致、哪些可以接受秒级延迟,并在业务代码里显式标注——GORM 只负责路由,不替你做架构决策。

















