thinkphp事件机制仅为消息分发管道,需在监听器中主动采集数据;db::listen()默认不捕获事务内多条sql,应结合microtime与db::getlog实现全量监控;推荐在think\responsesend事件中计算完整请求耗时并写入响应头;缓存统计应使用cache::getinfo()而非cache::has(),高频key须用tag管理;指标采集须与监听器解耦,交由外部系统或异步队列处理。

ThinkPHP 事件机制本身不提供数据监控能力,它只是消息分发管道;要实现监控,必须把事件当「埋点开关」用,在监听器里主动采集、计算、上报数据。
Db::listen() 捕获 SQL 耗时但漏掉事务内语句
默认情况下,Db::listen() 只对单条成功执行的 SQL 触发回调,事务中多条语句会被 PDO 批次合并,只报一次事件。这不是 bug,是底层驱动行为。
- 开发环境可临时关闭预处理模拟:
'options' => [PDO::ATTR_EMULATE_PREPARES => true],让每条 SQL 独立触发 - 生产环境更可靠的做法:在事务开始前调用
microtime(true)记起点,commit/rollback 后遍历Db::getLog()拿所有语句和时间戳 - 别依赖
$explain参数——它默认不开启,且生产环境 EXPLAIN 开销大,建议仅在配置database.enable_explain为 true 时才拼入日志
think\ResponseSend 事件才是请求耗时黄金点位
用 $_SERVER['REQUEST_TIME_FLOAT'] 作起点、在 think\ResponseSend 事件回调里取终点,能覆盖输出缓冲、Gzip 压缩等环节,比中间件或控制器里手动计时准得多。
- 必须在
app/Providers/AppServiceProvider.php的boot()方法里注册监听,不能放中间件——应用实例可能未就绪 - 终点时间必须在
$response->send()调用前获取,否则漏掉 header 发送、body flush 等真实耗时 - 写入响应头推荐:
$response->header('X-Response-Time', round(($end - $start) * 1000, 2) . 'ms'),方便浏览器 DevTools 或 Nginx 日志直接提取
自定义事件监听器里做缓存命中率统计要绕过 Cache::has()
Cache::has() 本身是一次 Redis 网络调用,用它判断再 get,等于双倍开销,且无法区分「key 不存在」和「连接失败」两种 null 返回。
- Redis 驱动下优先用
Cache::getInfo()拿hits/misses计数,它不走网络,只读本地状态 - 高频 key 必须加
Cache::tag(),失效时用Cache::tag('xxx')->clear(),别碰del cache:*—— Redis 不支持通配符删除,会扫全库 - 配置里显式设
'timeout' => 0.1(单位秒),避免单次 Redis 卡顿拖垮整个请求
真正难的是把事件监听和指标采集解耦:监听器里只做轻量数据收集,聚合、告警、可视化必须交给外部系统(如 Prometheus + Grafana)或异步队列。硬塞逻辑进去,升级时一改事件签名就全崩。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











