codeigniter 4 本身不内置读写分离能力,必须手动定义 write 和 read 两个独立数据库组并由业务代码显式路由;需在 app/config/database.php 中分别配置 hostname、username 等参数,通过 $db->getconnection('write') 或 $db->getconnection('read') 在 service 层动态获取连接,且关键读取须强制走 write 组以保证强一致性。

CI4 本身不内置读写分离能力,必须靠外部组件或手动控制路由。直接在 Database 配置里写两套连接、再靠业务代码判断走哪个,是常见但容易出错的做法。
CI4 中如何配置主从数据库连接
CodeIgniter 4 的 app/Config/Database.php 支持定义多个数据库组,但默认只启用一个。要支持读写分离,需手动定义至少两个组:write 和 read,并确保它们使用不同 host/port/username。
- 不能复用同一组连接配置的
hostname和port,否则起不到分离效果 -
DBGroup名称必须唯一且显式声明,比如'write'和'read',不能只靠default - CI4 不会自动识别“主库”“从库”,所有分组都是平等的,路由逻辑完全由你控制
示例片段:
public $write = [
'DSN' => '',
'hostname' => 'db-master.example.com',
'username' => 'master_user',
'password' => 'xxx',
// ...
];
public $read = [
'DSN' => '',
'hostname' => 'db-slave1.example.com',
'username' => 'slave_user',
// ...
];
如何在 Model 或 Service 层动态切换 DBGroup
CI4 的 BaseModel 默认绑定 default 组,不支持运行时切换。想让读操作走 read、写操作走 write,必须绕过 BaseModel 的自动连接管理,改用 Database 实例手动获取连接。
- 在 Service 层构造函数中注入
Database工厂(CodeIgniter\Database\ConnectionInterface不够用,得用CodeIgniter\Database\Database) - 写操作:调用
$this->db->getConnection('write') - 读操作:调用
$this->db->getConnection('read') - 注意:每次调用
getConnection()都会新建连接对象,不要长期持有,用完即弃
避免在 Controller 里硬编码 DBGroup 名字,建议封装成 ReadWriteDB 工具类,统一处理 fallback 逻辑(比如从库不可用时自动降级到主库读)。
主从延迟下如何保证关键读取一致性
CI4 没有类似 Sharding-JDBC 的 HintManager,也没有透明的“强读主库”机制。一旦出现刚写完就查不到的情况,只能靠业务层兜底。
- 对注册、支付回调、密码修改等场景,强制后续读请求走
write组,哪怕只是查一条记录 - 不要依赖时间戳或 sleep(0.5),MySQL 主从延迟波动大,0.5s 可能不够,也可能过度等待
- 可结合 session 或 request header 标记“刚发生写操作”,中间件拦截后自动改连主库
- 更稳妥的方式是:把这类读请求和写请求放在同一个事务上下文里(如果业务允许),用主库事务保证可见性
例如用户注册后立即登录,登录验证逻辑必须用 $db->getConnection('write'),而不是默认或 read 连接。
为什么别在 CI4 的过滤器或构造函数里做读写路由
CI4 的 Filter 执行时机在路由匹配之后、Controller 构造函数之前;而 Controller 构造函数又在方法执行前。这两个阶段都还没法知道当前请求是读还是写——除非你把 HTTP 方法(GET/POST)当唯一依据,但这在 RESTful 场景下极易误判(比如 GET /users/123 可能触发缓存更新逻辑)。
- 路由判断应发生在具体业务方法内部,或封装在 DAO 层方法签名里(如
findUser($id, $forceMaster = false)) - 构造函数里注入
Database实例时,只能拿到工厂,不能预设连接组;真正连接建立发生在第一次 query 时 - 如果在构造函数里就调用
$this->db->table()->get(),它会走默认组,不是你想要的 read/write 分离
真正的读写分离决策点,永远在数据访问那一刻,而不是请求入口。这点和代理层(如 MaxScale)有本质区别——CI4 是应用层路由,不是网络层路由。











