不能靠Echo路由机制自动分配连接池,因其HTTP层无数据库意图,无法安全推断主从库选择;必须手动初始化多个*sql.DB实例,按业务语义封装DAO并显式注入handler,由方法签名决定使用masterDB或slaveDB。

不能靠 Echo 路由机制自动分配连接池——连接池是数据库驱动层概念,Echo 本身不管理 DB 实例,必须手动初始化并按业务路由显式绑定。
为什么不能用 e.GET 或中间件自动切换连接池
Echo 的路由和中间件只处理 HTTP 请求生命周期,echo.Context 不携带数据库意图。你无法从 c.Request().URL.Path 安全推断该走主库还是从库:
-
GET /orders看似只读,但若 DAO 内部用了SELECT FOR UPDATE,就必须走主库,否则 MySQL 报ERROR 1290 (HY000): The MySQL server is running with the --read-only option -
POST /users/batch-import是写操作,但实际可能只查配置、发消息,全程读从库更合理 - 事务内所有 SQL 必须走同一 DB 实例,而路由层根本不知道 handler 是否启用了事务
正确做法:在 handler 依赖中注入指定连接池
核心是把不同用途的 *sql.DB 实例(如 masterDB、slaveDB、reportDB)作为结构体字段或参数传入 handler,而不是在 handler 里动态选库。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 启动时分别调用
sql.Open("mysql", dsnMaster)和sql.Open("mysql", dsnSlave),得到两个独立变量 - 为每个实例单独调参:
masterDB.SetMaxOpenConns(50)、slaveDB.SetMaxOpenConns(200);masterDB.SetWriteTimeout(2 * time.Second)、slaveDB.SetReadTimeout(8 * time.Second) - 封装 DAO 层,方法签名明确表达语义:
GetUserByID(ctx, id)内部用slaveDB,CreateOrder(ctx, o)内部用masterDB - 避免这种反模式:
if strings.HasPrefix(c.Request().Method, "GET") { db = slaveDB }——它会在事务、强一致读等场景下立刻崩
如何让不同路由使用不同 DAO 实例
不是“给路由配池”,而是“给 handler 注入对应 DAO”。例如:
type UserHandler struct {
readerDAO *UserReaderDAO // 用 slaveDB 初始化
writerDAO *UserWriterDAO // 用 masterDB 初始化
}
<p>func (h *UserHandler) GetUsers(c echo.Context) error {
users, err := h.readerDAO.List(c.Request().Context())
// ...
}</p><p>func (h *UserHandler) CreateUser(c echo.Context) error {
err := h.writerDAO.Create(c.Request().Context(), user)
// ...
}
</p>
- 注册路由时直接传实例:
e.GET("/users", userHandler.GetUsers)、e.POST("/users", userHandler.CreateUser) - 如果用依赖注入容器(如 Wire、Fx),确保
UserReaderDAO和UserWriterDAO分别绑定不同的*sql.DB - 不要用全局变量或闭包捕获 DB 实例——goroutine 安全性和测试性都会出问题
真正容易被忽略的是:连接池不是“按路由路径”切分的,而是“按数据一致性要求”切分的。一个 /users 路由下可能同时存在强一致读(需主库)、最终一致读(可从库)、报表导出(需专用 reportDB)——这些必须由 DAO 方法签名和调用链决定,而不是 URL 字符串匹配。










