ci4 的 database 类不原生支持读写分离,需手动实现连接池与路由逻辑;应封装 dbrouter 统一管理主从连接,处理复制延迟、健康检查及事务边界问题。

CI4 的 Database 类不原生支持读写分离
CodeIgniter 4 的 Database 类默认只维护单个连接,$db 实例始终指向一个配置项(如 default)。它没有内置的主从路由逻辑,也不会自动识别 SELECT 或 INSERT 并分发到不同服务器。试图直接在 app/Config/Database.php 中填多个 host 并指望框架自动分流,结果只会是连接失败或始终连到第一个。
必须手动实现连接池 + 路由逻辑
CI4 下读写分离需在应用层显式管理两套连接:一套指向主库(write),一套或多个指向从库(read)。关键点在于封装好“何时用哪个连接”:
-
write连接仅用于insert()、update()、delete()、query()(含写语句)等操作; -
read连接用于select()、get()、builder->get()等纯读操作; - 强一致性场景(如刚写完立刻查)必须强制走
write连接,否则可能读不到最新数据; - 建议封装一个
DBRouter服务类,统一提供getWriteInstance()和getReadInstance()方法,避免散落在各处的new Database()调用。
从库负载均衡不能只靠随机选
简单用 array_rand() 随机挑一个从库,在流量突增或某台从库响应变慢时容易导致请求堆积。更稳妥的做法是:
- 维护一个从库健康状态缓存(例如基于 ping 延迟或最近 5 次查询平均耗时);
- 优先选择延迟最低且可用的从库,而非纯随机;
- 若所有从库都不可用,应降级 fallback 到主库读(但需记录告警,避免主库被读压垮);
- 注意 CI4 的
Connection对象不是线程安全的,不要在协程或异步上下文中复用同一实例。
复制延迟会直接影响业务逻辑
MySQL 主从异步复制带来的延迟(常见 100ms–2s)在 CI4 应用中不会被自动感知。你写的代码如果没做适配,就可能出现“用户提交订单 → 页面跳转查订单 → 查不到”的问题:
- 对“读己所写”类操作,必须显式调用
$this->dbWrite->table()->get(),而不是$this->dbRead; - 可考虑在 session 或请求头中打标(如
X-Require-Master-Read: true),让中间件自动切换连接; - 不要依赖从库做唯一性校验(如注册时查用户名是否存在),这类判断必须走主库;
- 监控
Seconds_Behind_Master是必要的,但 CI4 本身不提供该指标采集,需额外通过query("SHOW SLAVE STATUS")定期拉取。
transStart()/transComplete() 只作用于当前 Connection 实例,跨连接的事务无法保证原子性。这意味着你不能在一个事务里混用 write 和 read 连接,否则会出错或产生未定义行为。











