gin本身不提供主从分离支持,因其仅为http路由框架,不管理数据库连接或sql路由;主从分离需在dao/orm层实现,通过显式维护主从两个*sql.db实例并按需调用。

为什么 Gin 本身不提供主从分离支持
Gin 是一个 HTTP 路由框架,它不负责数据库连接管理,更不会自动识别 SELECT 或 INSERT 并路由到不同实例。主从分离必须在 DAO 层或 ORM 层实现,Gin 只是把请求交给你,剩下的全靠你自己组织数据访问逻辑。
常见误区是试图在中间件里“切换 db 实例”,但 Gin 中间件无法改变你业务代码中已初始化的 *sql.DB 或 *gorm.DB 变量——它们是值传递或独立持有的。
用 sql.Open 手动维护主从两个 *sql.DB 实例
最轻量、最可控的方式:显式创建主库(写)和从库(读)连接池,业务代码按需选用。
- 主库只用于
Exec、QueryRow(带写意图的)、事务内操作 - 从库只用于
Query、QueryRow(纯读),且需注意事务中不能用从库(否则可能读到旧数据) - 两个
*sql.DB必须分别调用SetMaxOpenConns等参数,从库连接数通常可设更高 - 避免共用同一个 DSN:主从延迟下,刚写入就从从库查,大概率查不到——这不是框架问题,是业务逻辑缺陷
示例:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
var (
masterDB *sql.DB
slaveDB *sql.DB
)
func initDB() {
var err error
masterDB, err = sql.Open("mysql", "user:pass@tcp(10.0.1.10:3306)/mydb?writeTimeout=5s")
if err != nil { ... }
masterDB.SetMaxOpenConns(20)
slaveDB, err = sql.Open("mysql", "user:pass@tcp(10.0.2.20:3306)/mydb?readTimeout=5s")
if err != nil { ... }
slaveDB.SetMaxOpenConns(50)
}
在 Gin handler 中如何安全选择读/写 DB
不要依赖“方法名含 Get 就走从库”这类弱约定。真实场景中,有些 GetUser 需要强一致性(比如刚改完密码立刻登录),就必须走主库。
- 推荐方式:显式传参,例如
GetUser(ctx, userID, useMaster: true) - 或封装一层 Repository 接口,内部根据上下文(如是否在事务中、是否要求强一致)动态选库
- 绝对避免全局变量切换当前 DB——并发下会相互覆盖
- Gin 的
c.Request.Context()可以携带自定义 key(如ctx := context.WithValue(c.Request.Context(), dbKey, masterDB)),但仅限单次请求生命周期,别滥用
用 GORM v2 实现主从时的三个关键配置点
GORM 自带 Resolver 支持主从,但默认行为容易踩坑:
- 必须启用
resolver.Register并设置Resolve函数,否则始终走默认 DB -
Resolver的Resolve函数接收*gorm.Statement,可通过stmt.SQL.String()判断是否为SELECT,但注意:预编译语句里看不到完整 SQL,建议结合stmt.ReflectValue.Kind()和stmt.Table做白名单判断 - 事务中
resolver默认失效(GORM 强制使用当前事务绑定的 DB),这是对的——你本就不该在事务里读从库
片段示意:
db, _ := gorm.Open(mysql.Open(dsnMaster), &gorm.Config{
Resolver: resolver.NewResolver(resolver.Config{
Strategy: func(stmt *gorm.Statement) string {
if stmt.SQL.String() == "" || strings.HasPrefix(stmt.SQL.String(), "SELECT") {
return "slave"
}
return "master"
},
}),
})
db.Use(resolver.Register(resolver.Config{
Replicas: map[string]gorm.Dialector{
"slave": mysql.Open(dsnSlave),
},
}))
主从分离不是加个中间件就能跑通的事。延迟容忍、事务边界、连接池水位、SQL 解析可靠性——每个点都得结合你的业务读写比例和一致性要求去权衡。尤其当从库挂掉时,是报错还是自动降级?GORM 的 resolver 不会帮你做这个决策。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










