echo框架本身不提供读写分离能力,因其仅为http路由框架,不解析sql、不管理数据库连接;必须在dao层显式维护主从两个*gorm.db实例,并按sql语义或业务方法命名(如getlatestorder走主库、getorderhistory走从库)手动路由。

Echo 框架本身不内置读写分离能力,必须在应用层手动实现连接路由——这是最直接、可控性最强的方式,也是生产环境主流选择。
为什么不能靠 Echo 自动识别 SELECT/INSERT
Echo 是 HTTP 路由框架,不是数据库中间件。它不解析 SQL,也不持有数据库连接池。echo.Context 里没有 DB 字段,所有数据库操作都依赖你注入的 ORM(如 Gorm)或原生 sql.DB 实例。所谓“读写分离”,本质是你自己决定:这一条查询该用哪个 *sql.DB 实例执行。
- 常见错误:试图在中间件里根据请求路径(如
/api/users)硬编码读/写逻辑——这和 SQL 类型无关,极易误判(比如POST /api/users是写,但GET /api/users?include=posts可能触发复杂 JOIN 查询,仍应走从库) - 真正可靠依据只有 SQL 语句类型:以
SELECT开头(忽略注释和空格)且不含FOR UPDATE或LOCK IN SHARE MODE的,才视为可路由到从库 - Gorm v1.25+ 提供了
Session和WithContext配合自定义Resolver的能力,但需自行实现判断逻辑,不能开箱即用
Gorm + Echo 手动管理主从连接池
你需要显式创建两个独立的 *gorm.DB 实例:一个连主库(writeDB),一个连从库(readDB),并在业务代码中按需选用。Gorm 不会自动帮你切换,必须主动调用 Use 或传入特定实例。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 初始化时分别调用
gorm.Open,注意设置不同server-id和连接参数;从库建议加&read-only=true到 DSN,防止误写 - 避免全局单例混用:不要把
writeDB和readDB都注册为同一个 DI 容器 key,否则容易在 service 层拿错 - 简单路由示例(非透明):
// 在 handler 中显式选择 if strings.HasPrefix(strings.TrimSpace(sql), "SELECT") { db = readDB } else { db = writeDB } db.Raw(sql, args...).Scan(&result) - 更稳妥做法是封装一层
Repo接口,例如UserRepo.FindByID(ctx, id)内部用readDB,UserRepo.Create(ctx, u)内部用writeDB,把路由逻辑收拢在数据访问层
事务内必须强制走主库
任何涉及 BeginTx、SavePoint 或 SELECT ... FOR UPDATE 的操作,都不能发往从库——MySQL 从库默认 read_only=ON,会直接报错 ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement。
- 事务上下文(
context.Context)里应携带明确标记,例如自定义 key:ctx = context.WithValue(ctx, dbRoleKey, "master"),后续 repo 方法检查该值决定用哪个 DB 实例 - Gorm 的
Session可绑定特定连接:db.Session(&gorm.Session{NewDB: true}).Exec("SELECT ..."),但要注意 session 不跨 goroutine 传递,需配合 context 使用 - 切勿在事务中混合使用主从连接:比如先用
readDB查一条记录,再用writeDB更新它——主从延迟会导致查到旧值,引发脏写
真正的难点不在连接怎么建,而在于如何让业务开发者不感知路由细节,又不破坏事务一致性。强行封装“自动路由”容易掩盖主从延迟带来的数据不一致风险,不如把读写语义显式暴露在 repo 方法命名里,比如 GetLatestOrder()(走主库)和 GetOrderHistory()(可走从库)——这才是多数团队踩坑后回归的务实做法。










