不能直接用database/sql做读写分离,因为其不区分读写语义,所有操作均走同一*sql.db实例;手动配主从连接池后调用方无法自动分流,事务中混用会破坏一致性,select for update发往从库则直接报错。

为什么不能直接用 database/sql 做读写分离
Go 标准库 database/sql 本身不区分读/写语义,所有 db.Query、db.Exec 都走同一个 *sql.DB 实例。你手动配两个连接池(一个主库、一个从库),但调用方根本不知道该走哪个——除非显式传参或改函数签名,否则无法在 ORM 层或 DAO 层自动分流。
常见错误是:在事务中混用主从连接,导致从库读到旧数据;或把 SELECT FOR UPDATE 发到只读从库,直接报错 ERROR 1290 (HY000): The MySQL server is running with the --read-only option。
- 事务内所有查询必须走主库(包括只读 SELECT)
- 非事务下的简单 SELECT 可走从库,但需保证从库延迟可控(比如
Seconds_Behind_Master ) -
database/sql的SetMaxOpenConns等参数要分别配置主/从,避免从库连接池过大挤占主库资源
用 sqlx + 自定义 DB 结构体实现路由逻辑
最轻量的方式不是引入完整中间件(如 vitess 或 shardingsphere),而是封装一个带上下文感知的 DB 类型,内部持有主库和从库两个 *sqlx.DB 实例,并通过调用栈判断是否在事务中。
关键点在于:不依赖注解或 SQL 解析,而是靠 Go 的 context 传递读写意图。例如:
// 读操作显式用 WithReadHint ctx := context.WithValue(context.Background(), db.ReadHintKey, true) rows, _ := db.Queryx(ctx, "SELECT id FROM users WHERE status = ?", 1) // 写操作默认走主库,无需额外标记 _, _ := db.Exec(ctx, "INSERT INTO users(name) VALUES(?)", "alice")
- 自定义
DB.Queryx方法检查ctx.Value(ReadHintKey),有则用从库,否则用主库 - 事务开启时(
db.BeginTx)自动绑定主库连接,且后续所有子查询都继承该连接,无视ReadHintKey - 从库连接池建议设为
MaxOpenConns = 主库的 2~3 倍,因为读请求通常远多于写
如何安全处理事务与读写混合场景
最常被忽略的是“事务中读取后立刻更新”的模式,比如先查用户余额,再扣款。如果查用了从库,而从库延迟导致读到过期余额,就会出资金问题。
这类场景必须强制走主库,哪怕只是 SELECT。解决方案不是禁止读从库,而是提供明确的 API 约束:
- 暴露
db.MasterQueryx(ctx, ...)和db.ReplicaQueryx(ctx, ...)两个方法,让调用方自己担责 - 在事务内调用
db.Queryx时,内部自动降级为MasterQueryx,无需额外判断 - 对
SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE这类语句,解析 SQL 前缀并强制路由到主库(可用strings.HasPrefix(strings.TrimSpace(sql), "SELECT")粗筛,再正则匹配锁关键字)
连接健康检查与故障转移怎么加
读写分离模块一旦上线,就面临从库宕机或延迟飙升的问题。硬编码主从地址不可靠,需要运行时探测。
建议每 30 秒用 PING 检查从库连通性,并执行 SELECT @@read_only 确认状态。若失败,临时剔除该节点,日志记录:replica '10.0.1.5:3306' marked unhealthy: dial tcp 10.0.1.5:3306: i/o timeout。
- 不要等第一次查询失败才切换,提前做健康检查
- 从库列表支持动态 reload(比如监听 etcd 或文件变更),避免重启服务
- 主库不可用时,整个模块应 panic 或返回明确错误,**绝不尝试把写请求发到从库**(即使从库已取消 read_only)
真正难的不是路由逻辑,而是让团队所有人理解“读从库不等于最终一致性”,尤其在金融、订单类业务里,那个 SELECT 是不是真能读从库,得看它前面有没有刚发生的写操作,以及下游 binlog 延迟监控是否接入告警。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











