go中数据库读写分离必须显式控制,因database/sql不解析sql语义,正则匹配易误判select for update等写操作;事务内须全程绑定masterdb的*sql.tx,gorm dbresolver需显式注册主从dialector并独立配置连接池与延迟感知。

Go 里没有“分布式数据库路由框架”能自动识别 SQL 并分发到主从库——database/sql 不解析语义,驱动不判断读写,所有路由逻辑必须由你显式控制。
为什么不能依赖框架自动识别 SELECT/INSERT
框架(包括 GORM、sqlx 甚至自研中间层)若靠正则匹配 SQL 字符串判断读写,会高频误判:SELECT ... FOR UPDATE、WITH RECURSIVE、INSERT ... SELECT 这类语句以 SELECT 开头但本质是写操作;ORM 生成的 SQL 常含注释、换行、大小写混用,正则完全不可靠。MySQL 遇到 SELECT FOR UPDATE 发往从库直接报 ERROR 1290 (HY000): The MySQL server is running with the --read-only option,PostgreSQL 报 cannot execute SELECT FOR UPDATE in a read-only transaction。
事务内必须全程绑定 masterDB,不能切库
一旦调用 masterDB.Begin(),后续所有 tx.Query、tx.Exec 必须走该 *sql.Tx 实例——它底层只连主库。常见错误是封装了 ReadDB() 函数,在事务里无意识调用:slaveDB.QueryRow("SELECT ..."),结果新开一个从库会话,刚 INSERT 的数据根本查不到。正确做法是:事务开始即确定连接来源,后续所有操作都基于 *sql.Tx 调用,不经过任何路由封装。
GORM v2 中 dbresolver 插件的正确用法
它不是“自动识别”,而是按调用方法类型硬编码路由规则:Create/Save/Update/Delete/Transaction 强制走 sources(主库),Find/First/Where 默认走 replicas(从库)。关键配置点:
-
db.Use(dbresolver.Register().Sources(&gorm.Dialector{...}).Replicas(&gorm.Dialector{...})),必须显式注册 - 每个
Dialector对应独立sql.DB,连接池参数(MaxOpenConns、ConnMaxLifetime)需分开设置 - 强一致读不能靠猜,得用
db.Clauses(dbresolver.Write).First()显式指定走主库 -
Raw("UPDATE ...")默认走从库,必须加.Clauses(dbresolver.Write)否则报错
主从连接池必须独立初始化和健康检查
共用 sql.Open 返回值或复制指针是无效的——*sql.DB 是有状态的连接池实例,每个库必须单独 sql.Open + 单独 SetMaxOpenConns + 单独 Ping()。主库适合小连接池(如 20)、短生命周期(5 * time.Minute)、敏感超时(WriteTimeout: 2s);从库可设大连接池(如 100)、长复用(ConnMaxLifetime: 10 * time.Minute)、宽松读超时(ReadTimeout: 10s)。更关键的是:从库节点要带延迟感知,不能简单轮询——得定期执行 SHOW SLAVE STATUS 或 pg_replication_slots,过滤掉 Seconds_Behind_Master > 10 或心跳失败的节点。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











