如何用 performance_schema 找到当前最慢的几条 SQL?
How to find the top N slowest SQL queries using performance_schema?
考察要点
这道题面试官想看你是否具备在线诊断和定位 MySQL 性能问题的实战能力。它考察的不仅仅是理论,而是你是否熟练使用 MySQL 自身提供的强大工具 performance_schema。核心是检验你对 SQL 聚合统计(Digest) 概念的理解,以及能否熟练查询 events_statements_summary_by_digest 这张关键表,并能区分总耗时与平均耗时两种分析维度。
核心概念
performance_schema (P_S) 是 MySQL 5.5+ 内置的性能监控引擎,它以较低的开销实时收集服务器运行过程中的性能数据。与慢查询日志(Slow Query Log)相比,它在内存中进行,对 I/O 影响更小,并且能提供更丰富的聚合信息。
-
SQL Digest (SQL 指纹)
performance_schema会将结构相同、仅参数不同的 SQL 语句进行“范式化”,生成一个唯一的哈希值(Digest)。这使得聚合分析成为可能。- 原始 SQL:
sql1SELECT name, age FROM users WHERE id = 1001;2SELECT name, age FROM users WHERE id = 2050;
- 范式化后的 SQL (DIGEST_TEXT):
sql1SELECT name, age FROM users WHERE id = ?;
所有符合这个模式的查询都会被归类到同一个 Digest 下进行统计。
- 原始 SQL:
-
关键表:
events_statements_summary_by_digest这张表是定位慢 SQL 的核心。它存储了按 Digest 聚合后的 SQL 执行统计信息。关键列名 含义 SCHEMA_NAME数据库名 DIGEST_TEXT范式化后的 SQL 语句 COUNT_STAR此类 SQL 的总执行次数 SUM_TIMER_WAIT此类 SQL 的总执行耗时(单位:皮秒) AVG_TIMER_WAIT此类 SQL 的平均执行耗时(单位:皮秒) SUM_ROWS_SENT总返回行数 SUM_ROWS_EXAMINED总扫描行数(非常关键,反映了索引效率) SUM_SORT_ROWS总排序行数 FIRST_SEEN/LAST_SEEN首次/末次出现时间 -
开启与配置 默认情况下,
performance_schema是开启的,但具体的事件收集器(instrument)和消费者(consumer)可能需要手动开启。sql1-- 开启 statement 相关的事件收集2UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'statement/sql/%';3-- 开启 statement 统计信息的消费者4UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%statements%';在
my.cnf中持久化配置是更推荐的做法。 -
P_S vs. 慢查询日志
特性 Performance Schema Slow Query Log 存储 内存(表) 文件 开销 较低(CPU/内存) 较高(磁盘 I/O) 模式 实时、聚合统计 离线、记录单条 优点 适合发现高频、总体影响大的 SQL 适合捕获偶发、执行时间超长的单次查询 重置 可通过 TRUNCATE清空统计文件追加,需手动或轮转
多种解法
解法 1:按总耗时排序,查找系统“病灶”
这种方法用于定位那些累积消耗资源最多的 SQL。可能单次执行很快,但执行频率极高,总体上是性能瓶颈。
- 适用场景:系统整体负载高,需要找到最主要的性能消耗点。
- SQL 实现:
sql1SELECT2 SCHEMA_NAME,3 DIGEST_TEXT,4 COUNT_STAR AS '执行次数',5 -- 将皮秒转换为更易读的毫秒6 ROUND(SUM_TIMER_WAIT / 1000 / 1000 / 1000, 2) AS '总耗时(ms)',7 ROUND(AVG_TIMER_WAIT / 1000 / 1000 / 1000, 2) AS '平均耗时(ms)',8 SUM_ROWS_EXAMINED AS '总扫描行数',9 SUM_ROWS_SENT AS '总返回行数',10 FIRST_SEEN,11 LAST_SEEN12FROM13 performance_schema.events_statements_summary_by_digest14WHERE15 SCHEMA_NAME IS NOT NULL AND SCHEMA_NAME != 'performance_schema'16ORDER BY17 SUM_TIMER_WAIT DESC18LIMIT 10;
- 复杂度:查询本身是对内存表的一次扫描和排序,速度很快,复杂度为 ,其中 N 是 Digest 的数量。性能开销主要在 MySQL 后台收集数据时。
- 索引依赖:无,
events_statements_summary_by_digest是内存表,不依赖磁盘索引。
解法 2:按平均耗时排序,查找单次“慢”查询
这种方法用于定位那些单次执行就很慢的 SQL,通常是索引缺失、查询计划差的典型慢查询。
- 适用场景:解决用户反馈的单次操作卡顿问题。
- SQL 实现:
sql1SELECT2 SCHEMA_NAME,3 DIGEST_TEXT,4 COUNT_STAR AS '执行次数',5 ROUND(AVG_TIMER_WAIT / 1000 / 1000 / 1000, 2) AS '平均耗时(ms)',6 ROUND(SUM_TIMER_WAIT / 1000 / 1000 / 1000, 2) AS '总耗时(ms)',7 -- 平均扫描行数 = 总扫描行数 / 执行次数8 ROUND(SUM_ROWS_EXAMINED / COUNT_STAR) AS '平均扫描行数',9 LAST_SEEN10FROM11 performance_schema.events_statements_summary_by_digest12WHERE13 SCHEMA_NAME IS NOT NULL AND SCHEMA_NAME != 'performance_schema'14 -- 过滤掉执行次数过少的,避免偶然抖动干扰15 AND COUNT_STAR > 516ORDER BY17 AVG_TIMER_WAIT DESC18LIMIT 10;
- 复杂度:同解法 1。
- 索引依赖:无。
解法 3:结合 sys schema,更便捷的查询(推荐)
MySQL 5.7.7+ 引入了 sys schema,它在 performance_schema 之上提供了一系列易于理解的视图和存储过程,是生产环境中的首选。
- 适用场景:日常性能排查,语法更友好,信息更全面。
- SQL 实现 (查询总耗时最高的 SQL):
sql1-- sys.statement_analysis 视图已经做好了单位转换和可读性格式化2SELECT3 db,4 query,5 exec_count,6 total_latency,7 avg_latency,8 rows_sent_avg,9 rows_examined_avg,10 first_seen,11 last_seen12FROM13 sys.statement_analysis14-- 默认按总耗时排序,可以省略 ORDER BY15-- ORDER BY total_latency DESC16LIMIT 10;
- 说明:
sys.statement_analysis本质上就是封装了类似解法 1 的查询,但提供了更友好的列名和格式(如total_latency直接是 '1.23 s' 这样的字符串)。面试中提及sysschema 会展示你跟进社区最佳实践的积极态度。
常见低分回答
- 只知道慢查询日志:回答“我会开启慢查询日志,然后用
mysqldumpslow工具分析”,这表明知识体系停留在 MySQL 5.5 之前,不了解现代 MySQL 的监控能力。 - 概念模糊,说不出关键表:回答“我会去查
performance_schema”,但当被追问查哪张表时,无法说出events_statements_summary_by_digest,这说明只是听过该概念,没有实操经验。 - 混淆总耗时和平均耗时:无法清晰解释为何要区分
SUM_TIMER_WAIT和AVG_TIMER_WAIT进行排序,不理解这两种排序背后不同的优化目标(优化系统总资源消耗 vs. 优化单次用户体验)。 - 忽略统计周期:
performance_schema的数据是自服务启动或上次TRUNCATE以来累积的。如果不提这一点,可能会基于陈旧的数据做出错误判断。高分回答会提到在分析前,可能需要执行TRUNCATE TABLE performance_schema.events_statements_summary_by_digest;来开启一个新的统计窗口。 - 无法获取带参数的 SQL:只知道
DIGEST_TEXT是范式化的 SQL,但不知道如何找到一个具体的、可执行的慢查询实例来进行EXPLAIN。高分回答会提到可以从events_statements_history_long表中根据DIGEST值找到具体的SQL_TEXT。
工程实践与优化
- 使用
sysschema:在 MySQL 5.7.7+ 版本,应优先使用sysschema。它不仅简化了查询,还提供了如sys.diagnostics存储过程,可以一键生成详细的诊断报告。 - 关注
rows_examined:在分析结果时,AVG_ROWS_EXAMINED(平均扫描行数)和AVG_ROWS_SENT(平均返回行数)的比值是判断索引是否高效的关键指标。如果前者远大于后者,通常意味着存在巨大的优化空间。 - 结合
EXPLAIN ANALYZE:定位到慢 SQL 后,下一步是获取一个具体查询实例(可从events_statements_history_long获取),并使用EXPLAIN ANALYZE(MySQL 8.0+) 或EXPLAIN来分析其执行计划,找出瓶颈(如全表扫描、未使用索引、文件排序等)。 - 控制 P_S 开销:在超高并发的生产环境中,P_S 的开销也需关注。可以通过
setup_instruments和setup_consumers表动态地、精细地控制监控范围,只在需要时开启重量级的监控项(如events_statements_history_long),分析结束后及时关闭。 - 周期性重置:对于长期运行的实例,P_S 中的统计数据会一直累积。可以设置定时任务(如每天凌晨)
TRUNCATE关键的 summary 表,以便分析特定时间窗口(如过去24小时)内的性能状况。
常见追问与延伸题目
-
追问:Performance Schema 和慢查询日志,在生产环境中你如何选择和搭配使用?
- 回答要点 1 (日常监控):我会默认开启 Performance Schema,并依赖
sysschema 的视图(如statement_analysis)进行常态化、低开销的监控。这能帮我快速发现系统中消耗资源最多的“热点”SQL。 - 回答要点 2 (专项排查):对于需要捕获特定偶发慢查询(例如,超过5秒的查询)并记录其完整上下文(执行时间、锁等待、具体参数)的场景,我会开启慢查询日志。慢日志文件便于离线分析和历史追溯。
- 总结:两者是互补关系。P_S 用于“宏观”和“实时”的性能剖析,发现高频、累积影响大的问题;慢查询日志用于“微观”和“事后”的精确诊断,捕获单次执行慢的“罪魁祸首”。
- 回答要点 1 (日常监控):我会默认开启 Performance Schema,并依赖
-
追问:P_S 的性能开销有多大?如果发现它影响了系统性能,你会如何调整?
- 回答要点 1 (开销来源):P_S 的开销主要来自两方面:内存(用于存储事件的环形缓冲区)和 CPU(执行检测逻辑的额外指令)。内存开销由
performance_schema_*_size系列参数控制;CPU 开销与开启的instruments数量和服务器 QPS 正相关。 - 回答要点 2 (调整策略):
- 精简
instruments:通过查询setup_instruments表,UPDATE ... SET ENABLED = 'NO'来禁用不必要的监控项。例如,如果我只关心 SQL 语句,可以禁用所有wait、stage、transaction相关的监控。 - 精简
consumers:events_statements_history_long记录每条 SQL,内存和 CPU 开销最大。在日常监控中可以关闭它 (UPDATE ... SET ENABLED = 'NO'),仅在需要抓取具体 SQL 样本时临时开启。summary表的开销则小得多。 - 调整内存:如果内存是瓶颈,可以适当调小
performance_schema_events_statements_history_size等参数的大小,但这会让你丢失更早的历史事件。
- 精简
- 回答要点 1 (开销来源):P_S 的开销主要来自两方面:内存(用于存储事件的环形缓冲区)和 CPU(执行检测逻辑的额外指令)。内存开销由
-
追问:你从 P_S 找到了慢 SQL 的 Digest,下一步怎么拿到一个可以立即执行并
EXPLAIN的具体 SQL?- 回答要点 1 (定位 Digest):首先,通过解法 1 或 2 的查询,从
events_statements_summary_by_digest表中找到目标慢 SQL 的DIGEST值。 - 回答要点 2 (查询历史表):然后,利用这个
DIGEST值去查询events_statements_history_long表。这张表记录了最近执行过的 SQL 原文(带参数)。sql1-- 假设从上一步查到 digest 值为 'abc...'2SELECT SQL_TEXT, CURRENT_SCHEMA, TIMER_WAIT, ROWS_SENT, ROWS_EXAMINED3FROM performance_schema.events_statements_history_long4WHERE DIGEST = 'the_digest_hash_you_found'5ORDER BY TIMER_WAIT DESC6LIMIT 1; - 回答要点 3 (执行分析):从
SQL_TEXT字段取出的就是一条完整的、带真实参数的 SQL。可以直接复制出来,在前面加上EXPLAIN ANALYZE来分析其详细的执行计划和资源消耗,从而进行针对性优化。
- 回答要点 1 (定位 Digest):首先,通过解法 1 或 2 的查询,从