需继承ci_db_driver并重写query()方法,在其中用microtime(true)计时、替换$binds参数生成可读sql,结合累计耗时统计与log_message()安全写入日志。

CI自定义类里怎么开启慢查询日志并捕获执行语句
CodeIgniter 默认不记录慢查询 SQL,也不暴露原始执行语句到日志中。想监控“哪条 SQL 跑得慢”,必须在数据库驱动层拦截 query() 调用,并结合执行耗时做判断。
关键不是改配置文件,而是重写或包装 CI_DB_driver 的 query() 方法——因为只有在这里才能同时拿到 $sql、$binds(参数)、开始时间、结束时间和返回结果。
- 别试图只靠
log_threshold或log_path配置项生效:它们只控制“是否写日志”,不控制“写什么” - 不要在模型里每个
$this->db->get()前后手动打点:漏掉 ORM 封装的查询(如insert()、update())和事务内语句 - 推荐做法是继承
CI_DB_driver,覆盖query(),在内部用microtime(true)计时,超阈值时把$sql和$binds格式化后写入自定义日志文件
如何正确格式化带绑定参数的慢 SQL 日志
直接记录 $sql 字符串没用,因为 CI 用问号占位符 + $binds 数组执行,原生 SQL 不含真实值。日志里看不到实际执行了什么,就无法定位问题。
必须手动替换占位符。CI 自身有 _escape_bind() 类似逻辑,但未公开;稳妥做法是复用 CI_DB_driver::_bind_query() 的行为逻辑(注意:该方法是 protected,需在子类中调用)。
- 对
$binds中的null、string、int类型分别处理:null替换为NULL,字符串加单引号并转义,数字不加引号 - 避免直接用
str_replace()简单替换:多个 ? 时顺序错位会导致 SQL 错误,必须按$binds索引逐个替换 - 示例片段:
$final_sql = $sql; foreach ($binds as $val) { if ($val === null) { $final_sql = preg_replace('/\?/', 'NULL', $final_sql, 1); } elseif (is_string($val)) { $val = "'" . str_replace("'", "''", $val) . "'"; $final_sql = preg_replace('/\?/', $val, $final_sql, 1); } else { $final_sql = preg_replace('/\?/', (string)$val, $final_sql, 1); } }
为什么慢查询阈值设成 0.5 秒反而漏掉大量问题
阈值设太高(比如 2 秒),只能抓到明显卡顿;设太低(比如 0.1 秒),日志爆炸且掩盖真正瓶颈。0.5 秒是常见误判点——它忽略了高并发下“单条不慢、批量叠加就崩”的场景。
真实业务中,一个页面可能触发 10+ 次查询,哪怕每条都 0.3 秒,总响应也超 3 秒。所以不能只看单条,还得统计同一请求内所有查询耗时总和。
- 在自定义类中维护静态计数器和累计耗时,每次
query()后累加,__destruct()或钩子中输出总耗时 + 最慢 3 条 SQL - CI 的
post_system钩子可获取完整请求周期,但此时 DB 对象可能已销毁,必须提前在query()中把慢语句暂存到 CI 单例或全局数组 - 别依赖 MySQL 的
long_query_time:它只记录服务端解析后的语句,不包含 CI 绑定参数过程,也无法关联 PHP 请求上下文
日志写入失败或权限被拒的典型表现和修复
常见现象是:代码跑通、阈值触发、但日志文件始终为空,或者报错 Failed to open stream: Permission denied。
根本原因不是路径写错,而是 CI 日志目录(./application/logs/)默认权限为 755,而 Web 进程用户(如 www-data)没有写入权;更隐蔽的是,PHP 运行模式(CLI vs FPM)导致用户身份不同。
- 检查日志目录属主:
ls -ld application/logs/,确保与 Web 进程用户一致(如chown www-data:www-data application/logs/) - 不要用
file_put_contents($path, $content, FILE_APPEND)直接写:CI 自带的log_message()会自动处理权限和锁,应复用它:log_message('error', "[SLOW QUERY] {$final_sql} | time: {$elapsed}s"); - 如果必须写独立文件(如
slow_queries.log),务必在打开前调用mkdir($dir, 0755, true)并确认父目录可写
慢查询监控真正难的不是记录 SQL,而是让日志能稳定落盘、不干扰正常请求、还能快速反查上下文。参数替换、累计耗时、权限控制,三处任一出错,整套监控就形同虚设。











