多数据库实例需通过命名注册区分,如主库Name("master")、从库Name("slave");控制器注入时须用inject:"name"显式指定;事务必须绑定单一DB实例,跨库不支持;连接池需按实例单独配置。

多数据库实例必须注册为不同名称的依赖
Iris的DI容器不允许重复注册同一类型,*sql.DB这种基础类型不能直接多次app.Register。否则第二次注册会 panic:“duplicate registration for *sql.DB”。必须用命名注册(named registration)区分实例。
- 注册主库:
app.Register(func() *sql.DB { return masterDB }).Name("master") - 注册从库:
app.Register(func() *sql.DB { return slaveDB }).Name("slave") - 控制器字段注入时需显式指定名称:
MasterDB *sql.DB `inject:"master"`和SlaveDB *sql.DB `inject:"slave"` - 不写
inject标签或标签为空时,容器默认按类型匹配,无法区分同类型多个实例
控制器里怎么按需选择数据库实例
不能靠运行时判断“当前请求是读还是写”再动态切换*sql.DB指针——Iris依赖注入在控制器实例化时就已完成,字段值不可变。正确做法是把数据库选择逻辑下沉到 service 层,由 controller 调用带明确语义的方法。
- 定义 service 接口并实现两个版本:
UserRepo(主库写)、UserReader(从库读) - 分别注册:
app.Register[UserRepo](&UserRepo{DB: masterDB})和app.Register[UserReader](&UserReader{DB: slaveDB}) - 控制器注入两个依赖:
Writer UserRepo `inject:""`、Reader UserReader `inject:""` - GET 方法调用
c.Reader.GetByID(id),POST 方法调用c.Writer.Create(user)
事务必须绑定到单一 DB 实例,跨库事务不被支持
Iris 不提供分布式事务封装,sql.Tx 只能基于单个 *sql.DB 创建。如果你在同一个 handler 中先后调用主库和从库的 Exec,它们属于不同连接、不同事务上下文,无法保证原子性。
- 写操作涉及事务时,必须只使用主库实例:
tx, _ := c.MasterDB.Begin() - 不要尝试在事务中执行从库查询(如“写完后立刻查从库验证”),因为从库有复制延迟,且事务无法跨连接传播
- 需要强一致读的场景(如刚写入就查),应强制走主库:
c.MasterDB.QueryRow(...),而非c.SlaveDB - 若业务真需跨库协调,得自己实现补偿逻辑或引入消息队列,Iris 层不参与
路由级数据库路由(如 /api/v1/master/*)要手动拦截
Iris 没有内置的 database middleware 或自动路由分发机制。想实现“所有 /master/ 下的请求强制走主库”,只能靠自定义中间件 + 依赖替换,但要注意:controller 字段注入发生在实例化时,无法在中间件里改已注入的字段指针。
- 可行方案:不注入
*sql.DB,而是注入一个数据库工厂接口:DBFactory interface{ Master() *sql.DB; Slave() *sql.DB } - 中间件根据路径设置 ctx.Values().Set("db:role", "master")
- service 层方法内调用
factory.Master()或factory.Slave()获取实例 - 缺点:失去编译期类型检查,容易漏判;优点是灵活,可结合 header、query 参数等多维度路由
最易被忽略的一点:数据库连接池配置(SetMaxOpenConns 等)必须按实例单独设置。共用同一份配置会导致主库连接被从库查询耗尽,或从库空闲连接过多占用资源。每个 *sql.DB 实例都要独立调用 db.SetMaxOpenConns()。


















