php pdo需通过包装器、自定义语句类或中间件实现sql日志监控:1. 封装pdo类拦截prepare/execute,记录sql、参数、耗时、错误;2. 用pdo::attr_statement_class指定自定义pdostatement子类;3. 请求生命周期内批量聚合日志;4. 必须捕获异常并精确记录参数与时序。

PHP PDO 本身不提供内置日志监控功能,需通过包装、事件拦截或驱动扩展方式实现 SQL 执行记录与性能追踪。核心思路是统一拦截 PDO::prepare() 和 PDOStatement::execute() 等关键调用,捕获 SQL、参数、执行时长、错误信息等数据,再交由日志系统处理。
1. 使用 PDO 包装器(推荐)
创建一个自定义 PDO 封装类,在构造、预处理、执行等环节插入日志逻辑,无需修改业务代码,兼容性好。
- 继承或组合原生 PDO 实例,重写 prepare() 方法:记录 SQL 模板、开始时间,并返回封装后的 PDOStatement
- 封装 PDOStatement,重写 execute():记录绑定参数、执行耗时、影响行数、错误码/信息
- 支持按阈值采样(如只记录 >100ms 的慢查询)或按环境开关(开发开启,生产按需开启)
- 示例:$stmt = $pdo->prepare("SELECT * FROM user WHERE id = ?"); $stmt->execute([$id]); → 日志中可还原为 SELECT * FROM user WHERE id = 1
2. 利用 PDO::ATTR_STATEMENT_CLASS 自定义语句类
通过设置 PDO 构造选项,让所有 PDOStatement 实例自动使用你定义的子类,实现无侵入式增强。
- 定义 MyPDOStatement 继承 PDOStatement,重写 execute(),内部调用 parent::execute() 并记录指标
- 初始化 PDO 时传入:['PDO::ATTR_STATEMENT_CLASS' => ['MyPDOStatement', []]]
- 注意:该方式要求 PHP >= 5.2,且不能用于模拟预处理(PDO::ATTR_EMULATE_PREPARES = true 时部分行为受限)
3. 结合中间件或请求生命周期统一管理
在框架(如 Laravel、Symfony)或自研入口中,将日志聚合后输出,避免每条 SQL 都触发 I/O。
立即学习“PHP免费学习笔记(深入)”;
- 请求开始时初始化空日志容器(如数组),每次 SQL 执行追加结构化条目
- 请求结束前将全部 SQL 日志格式化为 JSON 或文本,写入文件、Syslog、或转发至 ELK / Sentry
- 可附加上下文:当前路由、用户 ID、Trace ID,便于问题定位
- 避免在循环中频繁写磁盘,建议批量落盘或异步发送
4. 补充:错误与异常捕获不可少
SQL 错误常被忽略,但恰恰是监控重点。需确保所有 execute()、fetch() 等操作都被 try/catch 包裹,或设置 PDO::ATTR_ERRMODE = PDO::ERRMODE_EXCEPTION。
- 捕获 PDOException 后,提取 errorInfo[1](SQLSTATE)、errorInfo[2](驱动错误码)、getMessage()(错误详情)
- 对重复错误(如唯一键冲突)可降级为 warning,对连接失败、语法错误等标记为 critical
- 结合 slow log + error log 双通道,覆盖性能与稳定性维度
不复杂但容易忽略的是参数还原和时序准确性——绑定参数需在 execute 前获取(可用 getAttribute(PDO::ATTR_DRIVER_NAME) 判断是否支持 bindParam 记录),耗时必须用 microtime(true) 精确到微秒级。做好这两点,日志才有真正分析价值。











