一、线上事故:一条SQL把CPU干到100%
2024年3月某天晚上22:17,监控告警弹出来:prod-db-01 MySQL CPU使用率98.7%,持续5分钟。登录服务器一看,SHOW PROCESSLIST; 刷出来30多个相同的SELECT语句,每条都在跑,每条都跑不完。
当时我打开MySQL Workbench 8.0.36,点开Performance Dashboard,看到CPU曲线是一条直线。再点Performance Reports,生成一份报告要等3分钟,报告出来只显示「有慢查询」,具体哪条SQL、跑了多少次、每次多慢——全都没有。那一晚我花了4小时,最后用3条命令定位到问题。这文章就把这个坑和完整方案写清楚。
二、问题拆解:Workbench到底能不能做慢查询分析
先给结论:Workbench能做,但只适合「事后看个大概」,不适合「线上排查」。它有三个硬伤:
| Workbench的坑 | 具体表现 | 影响 |
|---|---|---|
| 生成报告慢 | Performance Reports需要聚合大量历史数据,线上库跑一次要2-5分钟 | 业务高峰期不敢点 |
| 看不到完整SQL | Dashboard只显示「Slow Queries」计数,点进去只有TOP 10,且SQL被截断 | 拿不到完整SQL就没法优化 |
| 无法筛选和聚合 | 不能按「执行次数×平均耗时」排序找最该优化的SQL | 找错优化目标,白忙一场 |
Workbench的慢查询分析,本质是读取 performance_schema 和 sys 库的视图。它适合在本地开发环境看自己库的健康状态,不适合上生产。
三、方案对比:三种慢查询分析方法
我拿生产环境的真实慢查询数据(MySQL 8.0.35,innodb_buffer_pool_size=32G,慢查询日志记录数317条/小时),对比了三种方案:
方案A:MySQL Workbench Performance Reports
操作路径:Server → Performance Reports → Slow Query Reports。耗时120秒生成了报告。结果是一个柱状图+TOP 10列表。但TOP 10里的SQL被截断成前100个字符,索引情况、执行计划、参数变量全没有。想复制完整SQL?抱歉,没这功能。
方案B:mysqldumpslow(MySQL自带)
# 按平均查询时间排序,取前20条
mysqldumpslow -s at -t 20 /var/lib/mysql/prod-slow.log
这个工具秒出结果(317条日志用了0.3秒),能按平均耗时、执行次数、锁等待排序。但输出格式是摘要形式,SQL里的具体参数值被替换成「N」和「S」,看不到实际值,也没法直接拿到执行计划。
方案C:pt-query-digest(Percona Toolkit)
pt-query-digest /var/lib/mysql/prod-slow.log --limit=20 > slow_report.txt
1.2秒出结果,输出一份完整的分析报告,包含:每条SQL的Query ID、执行次数、平均耗时、总耗时占比、响应时间分布、甚至还有「Rows examined」和「Rows sent」的对比。配合 EXPLAIN 能直接定位到索引问题。这是我最终的推荐方案。
对比结论
| 方案 | 耗时 | 能否看到完整SQL | 能否筛出「最该优化的SQL」 | 适合场景 |
|---|---|---|---|---|
| Workbench Reports | 120秒 | 否(截断) | 否 | 本地开发环境 |
| mysqldumpslow | 0.3秒 | 部分(参数被替换) | 能 | 快速看一眼 |
| pt-query-digest | 1.2秒 | 是 | 是 | 线上排查、深度分析 |
四、完整代码实现:用pt-query-digest定位慢查询
4.1 开启慢查询日志(生产环境实测配置)
# MySQL 8.0.35,在 /etc/my.cnf 中添加以下配置,按需调整
[mysqld]
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2 # 超过2秒记入慢查询日志,生产建议1-3秒
log_queries_not_using_indexes = ON
min_examined_row_limit = 100 # 扫描行数超过100才记录,过滤小查询
# 修改后重启
systemctl restart mysqld
# 验证是否生效
mysql -uroot -p -e "SHOW VARIABLES LIKE 'slow_query_log%';"
mysql -uroot -p -e "SHOW VARIABLES LIKE 'long_query_time';"
4.2 用pt-query-digest生成完整分析报告
# 安装Percona Toolkit 3.5.5(CentOS 7/8 + Ubuntu 20.04均适用)
yum install percona-toolkit-3.5.5 # 或 apt-get install percona-toolkit
# 生成报告,limit=20表示只看前20条最耗时的SQL
pt-query-digest /var/log/mysql/slow.log --limit=20 > /tmp/slow_report.txt
# 如果想分析最近1小时的日志
tail -n 2000 /var/log/mysql/slow.log > /tmp/recent_slow.log
pt-query-digest /tmp/recent_slow.log --limit=20 > /tmp/recent_report.txt
4.3 从报告中找到「最该优化的SQL」
报告的核心是「Profile」部分,长这样:
# Profile
# Rank Query ID Response time Calls R/Call V/M Item
# ==== ================== ================ ====== ======= ===== =====
# 1 0xA1B2C3D4E5F6078 5123.4567 60.2% 2345 2.1845 0.01 SELECT order_info oi JOIN order_detail od ...
# 2 0xB2C3D4E5F60789A 1234.5678 14.5% 456 2.7074 0.01 SELECT user u WHERE u.phone = ?
# 3 0xC3D4E5F60789ABC 789.0123 9.3% 89 8.8653 0.00 UPDATE inventory SET stock = stock - ?
看Rank 1那条:执行了2345次,平均2.18秒,总耗时5123秒,占全部慢查询的60.2%。这条就是第一优化目标。按「响应时间占比」排序,比按「平均耗时」排序更能找到对系统影响最大的SQL。
4.4 拿完整SQL去分析执行计划
报告里每条SQL下面有完整的SQL文本,复制出来,到MySQL里跑EXPLAIN:
EXPLAIN ANALYZE
SELECT oi.order_id, oi.sku_id, od.product_name
FROM order_info oi
JOIN order_detail od ON oi.order_id = od.order_id
WHERE oi.create_time >= '2024-03-20 00:00:00'
AND oi.pay_status = 1
ORDER BY oi.create_time DESC
LIMIT 20;
4.5 优化方案:加联合索引
-- 生产环境执行前先确认:SHOW INDEX FROM order_info;
-- 加联合索引,(pay_status, create_time) 符合「等值在前,范围在后」原则
ALTER TABLE order_info ADD INDEX idx_pay_status_create_time (pay_status, create_time);
-- 验证优化效果
EXPLAIN SELECT ... -- type从ALL变成ref,rows从120万降到1564
4.6 效果数据(优化前后对比)
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 该SQL平均耗时 | 2.18秒 | 0.042秒 | 98% |
| 该SQL扫描行数 | 1,204,896行 | 1,564行 | 99.87% |
| MySQL CPU使用率 | 98.7% | 21.3% | 78.4% |
| 慢查询总数(每小时) | 317条 | 12条 | 96.2% |
| TPS(每秒事务数) | 486 | 1,203 | 147.5% |
数据来源:MySQL 8.0.35,8核16G云服务器,innodb_buffer_pool_size=32G。压测工具:sysbench 1.0.20(oltp_read_write模式,64线程,跑10分钟)。验证SQL:实际生产SQL,EXPLAIN ANALYZE和SHOW PROFILE确认rows变化。
五、Workbench还能用来干什么
Workbench不是不能用,但要用对场景。它在以下场景工作效率很高:
- 本地开发:直接可视化看表结构、跑临时SQL、可视化设计ER图。
- 小规模库巡检:连接开发库,看Server Status里的「Connections」「Queries」曲线。
- 导出/导入数据:用「Data Export/Import」功能做小库备份恢复。
但生产环境排查慢查询,别指望它。用 pt-query-digest 定位问题SQL,用 EXPLAIN ANALYZE 看执行计划,用 ALTER TABLE 加索引,这套流程才是标准打法。
六、避坑指南:我踩过的5个坑
坑1:Workbench连不上线上库,改了bind-address但忘了防火墙
MySQL 8.0默认bind-address=127.0.0.1,远程连接需要改成0.0.0.0,然后让DBA加防火墙规则。但改了bind-address之后systemctl restart mysqld起不来——因为/etc/my.cnf里还写了mysqlx-bind-address = 127.0.0.1,两边冲突。要一起改:
[mysqld]
bind-address = 0.0.0.0
mysqlx-bind-address = 0.0.0.0
坑2:Workbench的Performance Reports在测试环境跑没问题,生产直接卡死
生成本地开发库的报告只要10秒,但连生产库点一下「Performance Reports」,直接卡了3分钟,期间Workbench整个界面无响应。原因是它要读取performance_schema.events_statements_summary_by_digest,这个表在生产库上有上百万行,聚合查询跑了很久。解决方案:别在生产用Workbench做分析,用命令行工具。如果非要用,先 SET GLOBAL performance_schema_max_digest_storage=10000; 控制大小。
坑3:mysqldumpslow把SQL的参数全替换成N,实际排查时找不到对应代码
mysqldumpslow输出里所有字符串被替换成「N」,数值被替换成「N」,例如 WHERE user_id = N。这意味着你没法直接拿去EXPLAIN,也没法在代码里搜到这条SQL。pt-query-digest不会替换,它保留完整的SQL文本。所以排查问题时优先用pt-query-digest。
坑4:慢查询日志在业务高峰期爆炸,磁盘被打满
慢查询日志一旦开启,如果业务本身就是有大量慢查询,日志文件会疯狂变大。我遇到过2小时日志写了5.8GB,直接把/data分区撑爆。避坑:
# 用logrotate做日志轮转,每天切割,保留7天
# /etc/logrotate.d/mysql-slow
/var/log/mysql/slow.log {
daily
rotate 7
compress
missingok
create 660 mysql mysql
postrotate
/usr/bin/mysqladmin -uroot -p*** flush-logs
endscript
}
# 配合定时任务,每天凌晨3点备份分析
0 3 * * * /usr/bin/pt-query-digest /var/log/mysql/slow.log > /backup/slow_$(date +\%F).txt
坑5:long_query_time设置太小,导致慢查询日志全是无关紧要的小查询
一开始我把long_query_time设置为0.1秒,结果慢查询日志每小时记录2万+条,90%是单次查询0.1-0.3秒的小查询。真正要优化的「大查询」被淹没在噪声里。调整策略:生产环境设置long_query_time=2,先找到最耗时的SQL。优化一轮之后再逐步调小到1秒、0.5秒。不要一开始就追求「抓全部」。
七、总结一下排查慢查询的标准流程
- 登录数据库,先看
SHOW PROCESSLIST;确认当前有哪些SQL在跑。 - 开启慢查询日志(如果还没开),等1-2小时积累数据。
- 用
pt-query-digest生成报告,按Response time占比找到TOP SQL。 - 用
EXPLAIN ANALYZE分析执行计划,确认是否全表扫描、索引失效、临时表。 - 针对性优化:加索引、改写SQL、分页优化、修改表结构。
- 优化后等1小时,再跑一次
pt-query-digest,对比响应时间和CPU。
这套流程在3个生产库用过,每次都能在30分钟内定位到问题SQL。Workbench可以留着做开发用,排查慢查询交给专业工具。