gin本身不提供数据库主从路由能力,主从决策必须由业务层在handler中根据读写意图、请求方法、参数语义等动态判断,而非依赖路径匹配或中间件预置db连接。

Gin 本身不提供数据库主从路由能力,所谓「多DB主从动态路由」必须由业务层控制,而不是靠 Gin 的 HTTP 路由机制实现。
c.Request.URL.Path 直接匹配不适合主从路由
用 c.Request.URL.Path == "/user.save" 这类硬编码路径判断,只能区分接口行为,无法决定该走主库还是从库。主从决策依据通常是:读写意图(GET/POST/PUT)、是否带 ID、查询参数是否有 force_master=1 等,而非路径字符串本身。
- GET 请求多数走从库,但
/user/:id若带缓存穿透风险或强一致性要求,可能需强制走主库 - POST /user 可能是创建,必须走主库;但 POST /user/search 却是只读,应走从库
- 单纯依赖路径会误判:比如
/order/list是读,/order/export也是读,但后者可能触发主库慢查询,需要单独标记
推荐在 Handler 层做主从选择,而非中间件
中间件执行时,请求体还没解析,c.ShouldBindJSON 或 c.PostForm 都未调用,你无法知道这次请求到底是「查订单列表」还是「改订单状态」。把 DB 路由逻辑放在 Handler 里更可控:
- 先完成参数绑定(
c.ShouldBindQuery或c.ShouldBindJSON) - 根据业务语义判断读写类型,例如:
if req.Action == "update" || c.Request.Method != "GET" - 再调用封装好的 DB 获取函数,如
getDBByIntent("write")或getDBByIntent("read")
示例片段:
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
func orderHandler(c *gin.Context) {
var req struct {
Action string `form:"action" json:"action"`
ID uint `form:"id" json:"id"`
}
if err := c.ShouldBind(&req); err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": "bad request"})
return
}
db := getDBByIntent(req.Action, c.Request.Method)
// 后续使用 db.QueryRow 或 db.Exec...
}
避免在中间件里提前打开 DB 连接
常见错误是:中间件一进来就 db := getMasterDB(),然后塞进 c.Set("db", db)。这会导致两个问题:
- 所有请求(包括纯静态资源)都建立一次 DB 连接,浪费连接池
- 万一 Handler 最终没用到 DB(比如鉴权失败直接返回),连接白开了
- 无法按实际 SQL 类型切换主从:一个 Handler 里既查又改,中间件选的 DB 就不够用了
真正需要的是延迟获取、按需分配——用工厂函数代替预置实例,比如:
func getDB(ctx *gin.Context) (*sql.DB, error) {
method := ctx.Request.Method
if method == "GET" || method == "HEAD" {
return slaveDB, nil
}
return masterDB, nil
}
但注意:这个函数仍应在 Handler 内调用,而不是中间件。
主从路由的关键不在 Gin 路由表怎么写,而在你如何定义「读」和「写」的边界。路径只是入口,语义才决定数据流向。别让中间件替 Handler 做它不该做的决策。










