CI4的$db实例默认不支持读写分离自动路由,所有查询均发往主库;慢查询日志需在MySQL侧开启,CI4仅记录PHP层执行时间;监控从库延迟须直连执行SHOW SLAVE STATUS;CI4输出的SQL含占位符,无法直接对应MySQL慢日志中的实际指纹。

CI4 的 $db 实例默认不支持读写分离自动路由
CodeIgniter 4 的数据库类(Database)底层封装的是 PDO 或 MySQLi,所有查询都走同一个连接实例——也就是说,$db->query()、$db->table()->get() 全部发往配置中指定的「主库」。它本身没有内置的读写分离代理或从库连接池。
如果你看到某些项目实现了读写分离,那一定是手动干预的结果,比如:
- 在模型或服务层显式调用不同配置的
DB()实例:DB('slave')和DB('master') - 基于请求上下文(如 HTTP 方法、路由前缀)动态切换连接组
- 封装一个
ReadWriteDB类,在select类操作时自动选从库,其余走主库
但注意:这种手动路由不提供延迟感知、健康检查或故障自动降级。一旦从库宕机或延迟飙升,SELECT 就会直接报错或超时,而 CI4 不会帮你 fallback。
慢查询日志必须在 MySQL 侧开启,CI4 无法代劳
CI4 的 QueryLogger 只记录应用层执行的 SQL 和耗时($db->getLastQuery()->getDuration()),但它不等价于 MySQL 的慢查询日志(slow_query_log)。后者由数据库引擎内核触发,能捕获连接池复用、长事务、隐式转换等 CI4 完全不可见的低效行为。
正确做法是:
- 在 MySQL 配置中启用:
SET GLOBAL slow_query_log = 'ON',并设好long_query_time(建议先设为1) - 确认日志路径:
SHOW VARIABLES LIKE 'slow_query_log_file',确保磁盘有写权限 - 避免依赖 CI4 日志做性能归因——它漏掉太多关键信息,比如
Rows_examined、Using temporary、Using filesort
尤其要注意:CI4 的 getDuration() 返回的是 PHP 层面的执行时间,不含网络往返、锁等待、磁盘寻道等真实数据库开销,数值往往比 query_time 小得多。
监控从库延迟需绕过 CI4,直连 SHOW SLAVE STATUS
CI4 没有提供获取主从延迟的接口。想监控 Seconds_Behind_Master,必须单独建立一个只读连接,执行原生语句:
$slave = DB('slave');
$result = $slave->query('SHOW SLAVE STATUS')->getRow();
$delay = $result ? (int)($result->Seconds_Behind_Master ?? 0) : null;
常见陷阱:
- 误用主库连接执行该命令 → 返回空或报错
- 没处理
NULL值(例如 IO 线程停止时Seconds_Behind_Master为NULL) - 未加超时控制,从库 hang 住导致整个监控接口阻塞
生产环境建议用异步方式轮询,或改用 pt-heartbeat 这类成熟工具替代手工查状态。
慢查询分析不能只看 CI4 的 SQL 字符串
CI4 输出的 SQL(如 $db->getLastQuery()->getQuery())是带占位符的预处理模板:SELECT * FROM users WHERE id = ?。它掩盖了实际参数,无法对应到慢日志里的具体指纹(fingerprint)或 checksum。
真正要定位问题,得把 CI4 日志和 MySQL 慢日志对齐,重点比对:
- 执行时间(
query_timevsgetDuration()差值过大,说明瓶颈在 DB 层) - 扫描行数(
Rows_examined> 返回行数 10 倍以上,大概率缺索引) - 执行计划是否一致(用
EXPLAIN FORMAT=TREE对慢日志里抽样的完整 SQL 执行)
最容易被忽略的一点:CI4 默认关闭 log_threshold,不记录慢查询;而 MySQL 慢日志即使 CI4 没启日志也会照常记录——别把两者日志混为一谈。

















