应使用statementexecuted,它包含完整sql、绑定参数和执行耗时,覆盖所有sql操作;queryexecuted仅提供占位符模板,无法还原真实语句。

Hyperf 3.0 中监听 SQL 执行事件用 StatementExecuted 还是 QueryExecuted?
用 StatementExecuted。它包含完整 SQL 字符串、绑定参数($event->bindings)、执行耗时($event->time,单位毫秒),且能准确覆盖 INSERT/UPDATE/DELETE/SELECT 全部操作;QueryExecuted 不解析参数,$event->sql 是带 ? 占位符的模板,无法还原真实语句。
监听器注册方式必须是事件驱动式,不是模型钩子——Hyperf 的模型事件(如 saving)不保证与最终 SQL 一一对应(比如批量更新可能触发一次模型事件但生成多条 SQL)。
- 监听类需实现
ListenerInterface,并监听Hyperf\Database\Events\StatementExecuted - 不要在
__construct或初始化阶段依赖 DB 连接,因为事件可能在连接建立前就抛出 - 避免在监听器里做阻塞 I/O(如写文件、发 HTTP 请求),否则拖慢所有数据库操作
怎么从 StatementExecuted 事件中提取可读的完整 SQL?
不能直接用 $event->sql 拼接参数——Hyperf 的 StatementExecuted 已经做了参数插值,但前提是 DB 配置启用 log_sql => true 且 PDO 驱动支持参数展开。
确保 config/autoload/db.php 中有:
'options' => [
PDO::ATTR_EMULATE_PREPARES => true,
PDO::MYSQL_ATTR_DIRECT_QUERY => false,
],
'logger' => [
'log_sql' => true,
],
此时 $event->sql 就是带真实值的完整语句(如 INSERT INTO users (name, email) VALUES ('tom', 't@x.com'))。若仍是占位符,说明配置未生效或驱动不兼容(如某些 MySQLi 场景)。
- 生产环境慎用,敏感字段(密码、token)会明文落盘
- 临时调试可用
if ($event->time > 100) { ... }做条件日志,减少干扰 - 不要依赖
$event->sql做 SQL 注入检测——它已是执行后的结果,非原始输入
为什么 EventDispatcher dispatch() 无法跨服务收到 SQL 事件?
因为 StatementExecuted 是数据库组件内部触发的 PSR-14 事件,EventDispatcher::dispatch() 只在当前进程内存中同步分发,不会自动序列化、发消息队列、跨网络传输。
你在 A 服务里监听了 StatementExecuted,B 服务即使部署在同一台机器、用了相同代码,也收不到该事件——每个 Worker 进程维护独立的监听器列表,彼此完全隔离。
- 跨服务分析 SQL 耗时,必须手动桥接:A 服务捕获事件后,把
$event->sql、$event->time、$event->connectionName等关键字段序列化,发到 Redis 或 RabbitMQ;B 服务起消费者进程反序列化后,再本地dispatch() - 别试图用 Swoole Table 或协程 Channel 做跨进程传递——它们不解决服务边界问题
- Hyperf 官方
hyperf/event-dispatcher组件明确不承诺跨进程能力,文档和源码都印证这点
SQL 耗时不准或始终为 0 怎么办?
常见原因是 $event->time 未被正确采集。Hyperf 底层用的是协程 PDO,其耗时统计依赖于 Swoole 的钩子注入时机。如果 SQL 执行极快(
更可靠的做法是自己计时:
$start = microtime(true); $result = $db->select($sql, $bindings); $cost = round((microtime(true) - $start) * 1000, 3);
但注意:这会绕过事件机制,失去统一监听入口。折中方案是在监听器里 fallback 到 microsecond 级手动计时,仅当 $event->time === 0 时启用。
- 不要用
time(),精度太低;必须用microtime(true) - 避免在监听器里重复执行 SQL 来测速——那测的是第二次,不是原事件
- 协程环境下,
microtime是安全的,Swoole 已 patch 过时钟函数
真正难处理的是跨服务场景下,你既想集中看 SQL 耗时,又不想每个服务都埋点发 MQ。这时候得接受一个事实:Hyperf 的 SQL 事件天然是单机、单进程的,强行“跨”需要额外架构成本,没有银弹。











