MySQL原生慢日志是记录慢查询最可靠方式,需配置slow_query_log、long_query_time及log_queries_not_using_indexes;CodeIgniter的Profiler仅显示请求级耗时总和,易误判;钩子可捕获单条SQL真实执行耗时,但关键在结合EXPLAIN分析高频无索引查询。

CodeIgniter 本身不直接生成 MySQL 慢查询日志(slow_query_log),它也不解析或重写 MySQL 的 long_query_time 行为。你要的“慢查询日志”,得靠两层配合:MySQL 原生慢日志 + CodeIgniter 的 Profiler 或钩子辅助定位。否则只在 CI 层做“耗时判断”容易漏掉真实瓶颈(比如锁等待、全表扫描但执行快)。
怎么让 MySQL 自己记录慢查询
这是最可靠的第一步。CI 不插手,但必须确认数据库已启用并配置合理:
-
SET GLOBAL slow_query_log = 'ON'—— 开启后需重启连接才对新会话生效,推荐写入my.cnf持久化 -
SET GLOBAL long_query_time = 0.5—— 生产环境建议设为1.0或2.0,设成0会记录所有查询,IO 压力大 -
SHOW VARIABLES LIKE 'slow_query_log_file'—— 确认日志路径可写,且 Web 进程有读取权限(排查时常用) - 注意:MySQL 5.7+ 默认不记录未使用索引的查询,如需强制记录,加
SET GLOBAL log_queries_not_using_indexes = ON
Profiler 能帮你看到“谁慢”,但不是“为什么慢”
$this->output->enable_profiler(TRUE) 输出的是 CI 执行该次请求中所有查询的耗时总和,但它不区分网络延迟、锁等待、磁盘 I/O —— 这些都算进去了。常见误判场景:
- Profiler 显示某条
SELECT * FROM users耗时 800ms,但 MySQL 慢日志里没这条 —— 很可能卡在 PHP 层(比如结果集太大触发内存复制)或连接池阻塞 - 多条小查询合计耗时高,Profiler 列出每条都 2s —— 典型 N+1,需要看查询次数而非单条耗时
- 开启缓存后 Profiler 仍显示高耗时 —— 因为
cache_on()后的查询不走数据库,Profiler 记录的是文件读取时间,不是 SQL 执行时间
用钩子捕获实际执行的 SQL 和耗时(绕过 Profiler 局限)
想拿到更贴近 MySQL 实际行为的数据,得在 DB_driver.php 的 query() 方法前后埋点。关键不是记录日志,而是提取原始 SQL、绑定参数、执行前后 microtime(true) 差值:
// 示例:在 system/database/DB_driver.php 的 query() 方法内插入
$startTime = microtime(true);
$result = $this->simple_query($sql);
$elapsed = microtime(true) - $startTime;
<p>if ($elapsed > 0.5 && strpos($sql, 'SELECT') === 0) {
log_message('error', sprintf('[SLOW] %.3fs | %s | %s', $elapsed, $sql, print_r($this->binds, true)));
}
</p>
- 不要用
log_message('debug')记慢查询,生产环境log_threshold通常不打 debug 级别 - 避免在钩子里做复杂格式化(如拼接完整 SQL),会拖慢所有查询;先记原始
$sql和$elapsed,后续用脚本补全 - 注意事务中多次
query()可能共享同一连接,$elapsed是单次调用耗时,不是整个事务
真正要盯住的不是“慢”,是“不该慢”
很多 CI 项目上线后才发现:某接口平均 300ms,但其中 280ms 花在一条 WHERE status = ? 查询上 —— 而这张表根本没给 status 加索引。这类问题 Profiler 和慢日志都报不出来(因为单次不到阈值),只能靠人工结合 EXPLAIN 分析高频查询。你得定期跑:SELECT * FROM information_schema.PROCESSLIST WHERE TIME > 5,再比对 CI 日志里的 URI,才能揪出那些“不慢但高频”“不慢但无索引”的真凶。











