本文目录导读:

在 PHP 中抓取慢查询,通常涉及两个层面:数据库慢查询 和 PHP 自身执行慢,下面我分两部分详细说明,并给出最佳实践方案。
数据库慢查询(MySQL)
MySQL 慢查询日志(最常用)
-- 查看当前状态 SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time'; -- 开启慢查询(临时) SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; -- 记录超过1秒的查询 SET GLOBAL log_queries_not_using_indexes = 'ON'; -- 记录未使用索引的查询 -- 永久开启(my.cnf 配置) [mysqld] slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 log_queries_not_using_indexes = 1
查看慢查询日志:
# 使用 mysqldumpslow 工具分析 mysqldumpslow -s c -t 10 /var/log/mysql/slow.log # 实时查看 tail -f /var/log/mysql/slow.log
performance_schema(8.0+ 推荐)
-- 查询执行时间最长的SQL
SELECT
DIGEST_TEXT,
COUNT_STAR,
SUM_TIMER_WAIT/1000000000 AS total_seconds,
AVG_TIMER_WAIT/1000000000 AS avg_seconds
FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT NOT LIKE '%performance_schema%'
ORDER BY avg_seconds DESC
LIMIT 10;
使用 pt-query-digest(Percona Toolkit)
# 安装后使用 pt-query-digest /var/log/mysql/slow.log > slow_analysis.txt pt-query-digest --since="1 hour ago" /var/log/mysql/slow.log
PHP 应用层面慢查询
启用内置查询日志(最推荐)
<?php
// 在数据库连接初始化处添加
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 3, // 连接超时
];
// 使用 PDO 列子,自行封装记录慢SQL
class MySQLTrace {
private static $slowQueries = [];
public static function traceQuery($sql, $params = [], $threshold = 1.0) {
$start = microtime(true);
$result = self::query($sql, $params);
$elapsed = microtime(true) - $start;
if ($elapsed > $threshold) {
self::$slowQueries[] = [
'sql' => $sql,
'params' => $params,
'time' => $elapsed,
'time_str' => date('Y-m-d H:i:s')
];
// 可以记录到日志或发邮件
self::logSlowQuery();
}
return $result;
}
}
?>
使用框架内置的日志功能
Laravel 监听 SQL:
// AppServiceProvider.php
use Illuminate\Support\Facades\DB;
DB::listen(function ($query) {
if ($query->time > 1000) { // 超过1秒
Log::warning('慢SQL', [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time' => $query->time
]);
}
});
Symfony Doctrine:
// config/packages/doctrine.yaml
doctrine:
dbal:
connections:
default:
profiling: true
server_version: '%database_server_version%'
# 监听器
class SlowQueryLogger {
public function onKernelRequest(GetResponseEvent $event) {
$logger = $event->getRequest()->attributes->get('logger');
// 注册SQL日志记录器
}
}
使用 Tinker/自定义工具(小型项目)
<?php
// 创建一个通用查询函数
function query($sql) {
$start = microtime(true);
$result = DB::query($sql);
$elapsed = microtime(true) - $start;
if ($elapsed > 1) {
error_log("[SLOW] " . date('Y-m-d H:i:s') . " - " . $elapsed . "s - " . $sql . "\n", 3, "/tmp/slow_php.log");
}
return $result;
}
生产环境最佳实践组合
组合方案推荐
# 1. 系统监控(OPCache, Xdebug Profile) php -d xdebug.mode=profile -d xdebug.profiler_output_dir=/tmp/profiles -d xdebug.profiler_output_name=trace.%s.xt php test.php # 2. 使用 Blackfire / Tideways(商业工具价格高但功能强大) # 3. 引入 APM 工具(如 New Relic, Datadog,开源的用 Skywalking/Pinpoint) # 4. 日志记录 + 监控告警
轻量级解决(Laravel 优雅方案)
<?php
// app/Providers/AppServiceProvider.php
public function boot()
{
DB::listen(function ($query) {
$threshold = config('app.slow_query_threshold');
if ($query->time > $threshold) {
Log::channel('database')->warning('慢查询', [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time_ms' => $query->time,
'url' => request()->fullUrl(),
'session_id' => session()->getId()
]);
}
});
}
实时监控与告警
简单监控脚本
<?php
// monitor_slow.php - 定时执行
$cmd = "pt-query-digest /var/log/mysql/slow.log --since='5 minutes' --limit=5";
exec($cmd, $output, $return);
if ($return == 0) {
$slowQueries = implode("\n", $output);
if (strlen($slowQueries) > 100) {
// 发送邮件/钉钉通知
sendNotify("发现慢查询", $slowQueries);
}
}
终极建议
- 优先使用 MySQL 原生慢日志——最有效且影响最小
- 应用程序内增加慢查询探针——能记录到具体的业务上下文
- 定期分析与优化——用
pt-query-digest每周分析一次 - 建立SOP: 一旦发现慢查询,按照:
查询执行计划 → 索引检查 → SQL重写 → 缓存优化顺序处理 - 监控级联: 把慢查询和基础设施监控(CPU、内存、连接数)联动起来
实际案例参考
# 某电商系统,他们通过以下方式定位问题: # 1. MySQL slow log 里发现了一个跑了 25 秒的查询 # 2. app 内日志显示这个查询在 14:30 - 14:45 出现高频调用 # 3. 结合 Redis 缓存发现缓存穿透,最终通过改造 SQL 加索引和 Redis 三层缓存解决
这套组合拳基本能覆盖绝大多数 PHP 慢查询场景,关键是要尽早建立监控体系,而不是等到用户反馈才去排查。