4. SQL慢查询监控:MySQL慢查询日志配置、pt-query-digest分析、Grafana展示慢查询TOP N
慢查询是数据库性能劣化的首要信号。本章围绕“采集 → 分析 → 可视化”三条主线,讲解如何开启MySQL慢查询日志、使用pt-query-digest进行结构化解析,以及通过Grafana面板实时展示TOP N慢查询。掌握这套链路,你就能在用户感知到延迟之前定位问题SQL。
前置条件:MySQL 5.7+、Percona Toolkit已安装、Grafana 9.x+ 且已配置MySQL数据源。
4.1 MySQL慢查询日志配置
慢查询日志是MySQL原生提供的性能诊断工具,记录执行时间超过 long_query_time 阈值的SQL语句。以下为生产环境推荐配置:
| 参数名 | 推荐值 | 说明 |
|---|---|---|
| slow_query_log | ON | 开启慢查询日志 |
| slow_query_log_file | /var/log/mysql/slow.log | 日志文件路径,确保MySQL用户可写 |
| long_query_time | 1 | 单位秒,超过1秒即记录;分析阶段可临时设为0 |
| log_queries_not_using_indexes | ON | 记录未使用索引的查询,即使执行时间很短 |
| log_slow_admin_statements | ON | 记录慢管理语句(ALTER、ANALYZE等) |
| min_examined_row_limit | 1000 | 扫描行数超过此值才记录,过滤小表全表扫描 |
动态配置示例(无需重启MySQL):
-- 全局动态开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;
SET GLOBAL log_slow_admin_statements = ON;
SET GLOBAL min_examined_row_limit = 1000;
-- 确认配置生效
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
注意:生产环境不建议将
long_query_time 设为0,否则会记录所有查询,导致日志文件急剧膨胀,影响磁盘I/O。建议先设为1秒,待分析阶段再临时调低。
4.2 pt-query-digest分析慢查询
pt-query-digest是Percona Toolkit中最核心的分析工具,能将原始慢查询日志解析为结构化报告,按查询指纹(Query Fingerprint)聚合,并计算每个指纹的响应时间占比、执行次数、平均延迟等关键指标。
4.2.1 基本用法
# 分析慢查询日志,输出到终端
pt-query-digest /var/log/mysql/slow.log
# 输出到文件,便于后续处理
pt-query-digest /var/log/mysql/slow.log > /tmp/slow_report.txt
# 分析最近1小时的慢查询(配合日志轮转)
pt-query-digest --since=3600 /var/log/mysql/slow.log
# 输出JSON格式,便于Grafana或脚本消费
pt-query-digest --output=json /var/log/mysql/slow.log > /tmp/slow_report.json
4.2.2 报告解读
pt-query-digest报告分为三个主要部分:
| 报告段落 | 内容 | 重点关注 |
|---|---|---|
| Overall | 总执行次数、总耗时、唯一查询指纹数 | 总耗时是否异常增长 |
| Profile | 按响应时间占比排序的查询指纹列表 | TOP N查询的响应时间占比 |
| Query details | 每个查询指纹的详细统计(执行计划、行数、延迟分布) | Rows_examined与Rows_sent的比值 |
关键指标:响应时间占比(Pct of total time)超过20%的查询指纹应优先优化;Rows_examined / Rows_sent 比值超过1000说明索引缺失或查询逻辑需要重构。
4.3 Grafana展示慢查询TOP N
将pt-query-digest的分析结果导入MySQL表后,即可通过Grafana构建实时慢查询监控面板。推荐的数据模型如下:
4.3.1 数据导入表结构
CREATE TABLE slow_query_digest (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
fingerprint_md5 CHAR(32) NOT NULL,
fingerprint TEXT NOT NULL,
sample_query TEXT,
db_name VARCHAR(64),
exec_count INT NOT NULL DEFAULT 0,
total_time_sec DECIMAL(12,4) NOT NULL DEFAULT 0,
pct_total_time DECIMAL(5,2) NOT NULL DEFAULT 0,
avg_time_sec DECIMAL(10,4) NOT NULL DEFAULT 0,
rows_examined_avg INT NOT NULL DEFAULT 0,
rows_sent_avg INT NOT NULL DEFAULT 0,
first_seen DATETIME,
last_seen DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_last_seen (last_seen),
INDEX idx_total_time (total_time_sec DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.3.2 导入脚本示例
#!/bin/bash
# 每小时执行一次,将pt-query-digest结果导入MySQL
REPORT_FILE="/tmp/slow_report.json"
MYSQL_HOST="127.0.0.1"
MYSQL_USER="grafana"
MYSQL_PASS="your_password"
DB_NAME="monitor"
pt-query-digest --output=json /var/log/mysql/slow.log > $REPORT_FILE
# 使用Python解析JSON并写入MySQL(简化示例)
python3 << EOF
import json, pymysql
with open("$REPORT_FILE") as f:
data = json.load(f)
conn = pymysql.connect(host="$MYSQL_HOST", user="$MYSQL_USER",
password="$MYSQL_PASS", database="$DB_NAME")
cur = conn.cursor()
for query in data["queries"]:
md5 = query["fingerprint_md5"]
# 使用INSERT ... ON DUPLICATE KEY UPDATE 更新统计
sql = """INSERT INTO slow_query_digest
(fingerprint_md5, fingerprint, sample_query, db_name,
exec_count, total_time_sec, pct_total_time,
avg_time_sec, rows_examined_avg, rows_sent_avg,
first_seen, last_seen)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
exec_count=VALUES(exec_count),
total_time_sec=VALUES(total_time_sec),
last_seen=VALUES(last_seen)"""
cur.execute(sql, (md5, query["fingerprint"], query["sample"],
query.get("db",""), query["exec_count"],
query["total_time"], query["pct_total_time"],
query["avg_time"], query["rows_examined_avg"],
query["rows_sent_avg"], query["first_seen"],
query["last_seen"]))
conn.commit()
cur.close()
conn.close()
EOF
4.3.3 Grafana面板配置
在Grafana中创建Dashboard,添加以下面板:
| 面板类型 | 查询SQL | 展示目标 |
|---|---|---|
| Table | SELECT fingerprint_md5 AS 指纹ID, LEFT(fingerprint,80) AS 查询摘要, exec_count AS 执行次数, total_time_sec AS 总耗时(秒), ROUND(avg_time_sec,4) AS 平均耗时, ROUND(rows_examined_avg/rows_sent_avg,0) AS 扫描/返回比 FROM slow_query_digest ORDER BY total_time_sec DESC LIMIT 20 |
展示TOP 20慢查询明细 |
| Bar Gauge | SELECT fingerprint_md5 AS metric, pct_total_time AS value FROM slow_query_digest ORDER BY total_time_sec DESC LIMIT 10 |
TOP 10慢查询响应时间占比 |
| Stat | SELECT SUM(exec_count) AS 总执行次数, SUM(total_time_sec) AS 总耗时 FROM slow_query_digest WHERE last_seen > NOW() - INTERVAL 1 HOUR |
过去1小时慢查询总量 |
调优建议:在Table面板中启用“列排序”和“搜索”功能,方便DBA快速过滤特定数据库或关键词;Bar Gauge面板建议设置阈值:绿色 < 5%,黄色 5%~20%,红色 > 20%。
4.4 报警规则配置
基于慢查询数据,在Grafana Alerting中配置以下报警规则:
- 慢查询总量突增:过去5分钟慢查询执行次数 > 基线值的2倍,触发Warning。
- 单条查询超时:任意查询指纹的平均耗时 > 5秒,触发Critical。
- 全表扫描风险:rows_examined_avg / rows_sent_avg > 5000 且 exec_count > 100,触发Warning。
避坑指南:不要对慢查询总量设置固定阈值,应使用Grafana的“动态基线”或“同比/环比”功能,避免因业务流量波动导致误报。同时,pt-query-digest分析本身会消耗CPU资源,建议在从库或专用分析实例上执行。
通过本章配置,你已经构建了一条完整的慢查询监控链路:从MySQL日志采集,到pt-query-digest结构化分析,再到Grafana实时展示与报警。下一环节将进入索引优化与查询重写实战。