需手动通过Db::listen()注册监听器计算SQL耗时并记录,配合调用栈、参数、连接类型等上下文信息,结合合理阈值(如300ms起调)和敏感数据脱敏,才能实现有效慢查询日志。

如何开启 ThinkPHP 的慢查询日志
ThinkPHP 本身不内置“慢查询日志”功能,它只提供基础的 SQL 日志(show_sql)和调试日志,但没阈值判断。真要捕获“执行超时的 SQL”,得自己加钩子——最稳的方式是在数据库查询完成后的回调里做耗时判定。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 在
app/database.php或全局中间件中,用Db::listen()注册监听器,拿到 SQL、绑定参数、开始时间、结束时间 - 手动计算耗时:
$duration = microtime(true) - $start_time,再和你设定的阈值(比如 500ms)比对 - 别直接写文件,用
Log::write()写入独立通道(如slowsql),避免和主日志混在一起难排查 - 注意:
Db::listen()在 TP6.0+ 才稳定支持;TP5.1 需用think\db\Connection::setQueryHook(),且仅限 PDO 场景
为什么不能只靠 SHOW PROFILES 或 MySQL 慢日志
因为 ThinkPHP 是应用层框架,SQL 可能被拼接、复用、缓存,MySQL 原生慢日志看到的是最终执行语句,但找不到是哪个控制器、哪个模型方法触发的,更没法关联请求上下文(如用户 ID、URL、trace_id)。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- MySQL 慢日志(
slow_query_log)只能辅助验证,不能替代应用层日志 - TP 的
Db::getLastSql()在事务或批量操作中可能不准,尤其用了insertAll()或where()->update()时,返回的未必是实际执行的那条 - 如果开了查询缓存(
cache(true)),慢日志里根本不会出现这条 SQL——它压根没进数据库
Db::listen() 监听器里容易漏掉的关键字段
很多人只记 SQL 和耗时,结果查问题时发现无法复现:不知道参数是什么、不知道在哪调的、甚至不知道是否走读库。这些信息必须一并捞出来。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 一定要记录
$info['sql']、$info['time']、$info['params'](绑定参数)、$info['type'](select/insert/update/delete) - 用
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)截取两层调用栈,定位到具体 Controller 方法,别只写Db::listen所在文件 - 如果是读写分离环境,加个
$info['connection']字段,标清楚是mysql_read还是mysql_write - 避免记录敏感字段:检查
$params是否含密码、token、手机号,脱敏后再写日志
性能与误报:阈值设多少才合理
设太低(比如 50ms),日志爆炸,全是正常 JOIN 或小表 COUNT;设太高(比如 2s),真正卡顿的请求早超时了,根本来不及记录。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 新项目从 300ms 开始试,上线后看日志分布:如果 80% 的慢查询集中在 200–400ms,说明阈值偏高,往下调
- 区分场景设阈值:列表页允许 500ms,详情页建议 200ms,管理后台可放宽到 1s
- 别对所有 SQL 统一判断——
SELECT COUNT(*)天然慢,可单独加白名单逻辑跳过 - 注意:监听器本身有开销,单次调用增加约 0.02–0.05ms,高频接口(如每秒上千请求)要测压,避免日志逻辑反成瓶颈
慢查询日志不是开了就完事,真正难的是让每条记录都带上下文、可追溯、不拖慢主线程。参数怎么取、阈值怎么调、敏感信息怎么筛,三者漏一个,日志就变噪音。



















