thinkphp需自定义实现sql慢查询日志:启用log_sql,通过db::listen监听event_query事件,比对getquerytime()与阈值(如500ms),超时则写入独立slowsql日志通道。

如何开启ThinkPHP的SQL慢查询日志
ThinkPHP本身不内置“慢查询日志”开关,但可通过数据库配置 + 自定义日志钩子实现。关键不是等报错,而是主动让框架把执行时间超阈值的SQL记下来。
最直接的方式是在 database.php 配置中启用 trace 并配合 log_sql,但注意:这只会记录所有SQL,不区分快慢。真要“慢查询”,得自己加判断逻辑。
- 在
app/database.php中确保'log_sql' => true,且'sql_explain' => false(避免EXPLAIN拖慢本身) - 设置
'slow_time' => 500(单位毫秒),这个参数虽非官方配置项,但可在自定义Db类或事件监听里用到 - 不要依赖
think\db\Connection::getRealSql()直接拼日志——它不带执行耗时,必须结合think\db\Connection::getQueryTime()
用Db类事件监听捕获慢SQL(TP6+)
TP6起推荐用事件系统替代全局钩子。核心是监听 think\db\Connection::EVENT_QUERY,并在回调里比对执行时间。
在 app/Event.php 或服务提供者中注册:
Db::listen(function ($sql, $time, $explain) {
if ($time > 500) { // 慢于500ms
Log::channel('slowsql')->info(sprintf('[%s] %s', $time, $sql));
}
});
-
$time是真实执行毫秒数,TP6.0.10+才稳定返回整数,旧版本可能为浮点(如0.1234),需round($time * 1000)统一单位 - 别在监听里调用
Db::query()或任何可能触发新查询的操作,否则形成死循环 -
$explain参数默认为null,除非你显式开启'sql_explain' => true,但它会显著拖慢性能,仅调试时开
为什么Log::channel('slowsql') 要单独配通道
慢SQL日志量少但重要,混在 runtime/log/202406/xxxx.log 里极难定位。单独通道能隔离存储、独立轮转、方便对接ELK或Grafana。
- 在
config/log.php新增'channels' => ['slowsql' => [...]],类型建议用'single'或'daily',避免写入压力 - 路径别设成
runtime/slowsql/—— ThinkPHP默认禁止写 runtime 下非 log/ cache/ 日志目录,要么改runtime_path,要么把 slowsql 放进runtime/log/子目录 - 别给 slowsql 通道加
'level' => 'error',慢SQL不是错误,设成'info'才能被Log::info()写入
排查时发现SQL不慢但接口卡顿?查QueryTime之外的环节
Db::listen() 只反映PDO执行耗时,但真实瓶颈常在别处:连接池等待、PHP序列化大结果集、ORM模型关联预加载爆炸、甚至Redis锁阻塞。
- 先确认
$time值是否真高——如果日志里显示[2.3] SELECT * FROM user WHERE id=123,那问题不在SQL本身 - 用
debug_start('full')+debug_end('full')包裹整个Action,看总耗时分布;再用Debug::remark('after_db')插点定位 - TP6.3+ 可配合
think-trace扩展查看完整调用栈,但注意它默认不开SQL耗时列,需手动在trace.php配置'db' => true
慢查询日志只是入口,真正卡点往往藏在 QueryTime 之后的 PHP 层处理里——比如一个 toArray() 把10万行数据全转成数组,或者 with(['profile', 'orders', 'orders.items']) 触发N+1又没做懒加载控制。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











