codeigniter 4 不原生支持主从读写分离,需手动实现路由逻辑并显式校验主从数据一致性;无 rw_separate 或 deploy 配置项,不自动分发 sql 请求,也无 db::master() 等快捷方法。

CodeIgniter 4 不原生支持主从读写分离,必须手动实现路由逻辑;数据校验不能依赖框架自动完成,需在应用层显式比对主从结果。
CI4 没有 rw_separate 或 deploy 配置项
ThinkPHP 的 rw_separate 和 deploy 是其数据库连接器内置的开关,CI4 的 Database 类完全不识别这两个键。你在 app/Config/Database.php 里加了也无效,配置会被忽略。
CI4 只允许定义多个独立组(如 'default'、'slave1'、'slave2'),但不会自动根据 SQL 类型分发请求——SELECT 不会“自动”走到某个 slave 组。
- 所有查询默认走
default组,除非你显式调用$this->db->group('slave1') - 没有
Db::master()这类快捷方法,也没有 before_select 事件钩子 - 事务中无法自动锁定主库:
$this->db->transStart()后仍可能误用 slave 连接执行 SELECT
手动实现读写路由必须重写 Query Builder 或封装 DB 类
最稳妥的做法是继承 CodeIgniter\Database\BaseBuilder,在 get()、getResult() 等读方法里强制切换到从库连接;写操作(insert()、update()、delete())则始终用 default。
更轻量的方案是封装一个 DBRouter 类,统一接管连接选择:
class DBRouter
{
protected $dbMaster;
protected $dbSlaves = [];
public function __construct()
{
$this->dbMaster = \Config\Database::connect('default');
$this->dbSlaves[] = \Config\Database::connect('slave1');
$this->dbSlaves[] = \Config\Database::connect('slave2');
}
public function read(): \CodeIgniter\Database\BaseConnection
{
return $this->dbSlaves[array_rand($this->dbSlaves)];
}
public function write(): \CodeIgniter\Database\BaseConnection
{
return $this->dbMaster;
}
}
- 注意:每个
connect()返回的是新连接实例,不是单例,要避免频繁重建 - 不能复用同一个
BaseConnection实例做读写切换——PDO 连接不可跨语句类型重用 - 若用 Query Builder,需把
$builder->setConnection($conn)注入每条链式调用前
主从数据校验只能靠应用层主动发起
CI4 不提供 SHOW SLAVE STATUS 解析或延迟监控能力。你想知道从库是否落后,得自己查:
// 在从库连接上执行
$slave = \Config\Database::connect('slave1');
$result = $slave->query("SHOW SLAVE STATUS")->getRow();
$delay = $result->Seconds_Behind_Master ?? null;
- 值为
NULL表示未开启复制或 IO/SQL 线程异常 - 值 > 5 秒建议降级:后续读请求改走主库,直到
Seconds_Behind_Master == 0 - 校验关键行一致性?不能只比对 COUNT(*),要用
CHECKSUM TABLE user或 MD5( GROUP_CONCAT(...) ) —— 但大表会锁表 - 推荐做法:对强一致性要求的记录(如用户余额),SELECT 后立刻用主库再查一次 ID + version 字段比对
真正麻烦的不是配置,而是当主从延迟发生时,你得决定哪条 SELECT 必须等、哪条可以容忍旧数据——这个判断不在数据库里,而在你的业务代码分支里。











