必须维护两套独立的*sql.DB实例:主库(masterDB)用于写和强一致读,从库(slaveDB)用于最终一致性查询;共用连接池会导致连接老化不同步、故障互相拖累及主从延迟问题。

怎么在 Go 中配置主从数据库连接池?
读写分离不是框架自动完成的,得靠你显式控制连接来源。核心是维护两套独立的 *sql.DB:一套连主库(masterDB),一套连从库(slaveDB)。不能共用一个连接池,否则事务、主从延迟、连接复用策略都会出问题。
常见错误是只建一个 *sql.DB 然后动态改 DSN——这不行,DSN 是初始化时绑定的,运行时无法切换底层地址。
- 主库连接池要禁用
SetMaxIdleConns(0)或设较小值,避免长事务占满空闲连接 - 从库连接池可适当调高
SetMaxOpenConns,扛住读流量高峰 - 务必为两个连接池分别调用
SetConnMaxLifetime和SetConnMaxIdleTime,避免主从连接老化不同步
怎么让 ORM 或原生 SQL 自动走主库或从库?
Go 没有“透明读写分离”这种东西。所谓“自动”,其实是你在业务逻辑里做路由判断。主流做法是封装一层 DBExecutor 接口,暴露 Query(走从库)和 Exec(走主库)方法:
type DBExecutor struct {
master *sql.DB
slave *sql.DB
}
func (d *DBExecutor) Query(query string, args ...interface{}) (*sql.Rows, error) {
return d.slave.Query(query, args...)
}
func (d *DBExecutor) Exec(query string, args ...interface{}) (sql.Result, error) {
return d.master.Exec(query, args...)
}
注意:事务必须强制走主库,且不能混用从库连接。一旦开始 Begin(),后续所有操作都得基于该 *sql.Tx,不能再切到从库。
- 不要在事务中调用
Query方法——它默认用slaveDB,会 panic - 复杂查询如
SELECT ... FOR UPDATE必须走主库,否则报错ERROR 1290 (HY000): The MySQL server is running with the --read-only option - 如果用 GORM,别依赖
Replica配置自动分发;它的 replica 是只读副本池,但写操作仍需显式Session(&gorm.Session{Write: true})
主从延迟导致查不到刚写入的数据,怎么办?
这是读写分离最常踩的坑:用户注册后立刻查个人资料,返回空。MySQL 主从复制是异步的,延迟几十毫秒到几秒都正常。
解决思路不是消灭延迟,而是识别“需要强一致”的场景,主动切回主库查:
- 刚执行完
INSERT/UPDATE的同一请求内,后续SELECT应直连masterDB - 可以用上下文传标记,比如
ctx = context.WithValue(ctx, ctxKeyReadPreference, "master"),在 DAO 层读取并路由 - 对一致性要求极高的接口(如支付结果查询),直接跳过读写分离,全走主库
- 不要依赖
SELECT SLEEP(0.1)这类投机方案——延迟不可控,且浪费资源
如何验证主从流量是否真的分离了?
光看代码逻辑不够,得观测真实连接行为。最简单有效的方式是开启 MySQL 的 general log 或用 SHOW PROCESSLIST 实时抓连接来源:
在主库执行:SHOW PROCESSLIST,观察是否有应用连接出现在这里(应有,且集中在写操作);在从库执行同样命令,确认读请求连接确实打到了从库 IP。
- 生产环境慎开 general log,可用 Percona Toolkit 的
pt-query-digest分析慢日志,按客户端 IP 区分主从流量 - 给主库和从库连接加不同用户名(如
app_master/app_slave),再查information_schema.PROCESSLIST更易识别 - 如果发现从库连接数为 0,大概率是代码里所有查询都误用了
masterDB.Query
主从分离真正难的不是配连接,而是把“什么时候该读主库”这个业务语义,准确翻译成代码里的连接选择逻辑。漏掉一个刚写即读的路径,就可能引发线上数据不一致问题。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











