概述
摘要 :前文《分库分表与 ShardingSphere‑JDBC》展示了分布式数据分片方案,但无论架构如何演进,单条慢查询仍可能拖垮整个系统。如何在海量 SQL 中精准定位慢查询?如何从监控告警反推根因?如何避免"CPU 飙高就加索引"的盲目优化?本文从慢查询日志的底层格式出发,构建以 pt‑query‑digest + PMM + sys schema + EXPLAIN ANALYZE + performance_schema 为核心的五维诊断体系,并以前文的核心原理(索引、锁、优化器、复制)为理论支撑,形成一套可复用、可追溯的排查决策树。
文章组织架构
总览说明:全文 7 个模块从慢查询日志出发,依次展开五大诊断工具链,最后以四场景排查推演和面试题收尾,形成"数据采集→分析→关联→验证→根因"的完整闭环。
逐模块说明:
- 模块1 建立数据采集基础------慢查询日志的内部字段与生产配置,深入
log_output机制。 - 模块2 引入
pt‑query‑digest,将海量日志压缩为查询指纹并识别资源消耗最高的 SQL 模板,并辅以pt-index-usage。 - 模块3 利用 PMM 构建时间维度的宏观指标关联(QPS、Buffer Pool、锁、复制延迟),并设计 PrometheusRule 告警。
- 模块4 通过
sys schema的 20+ 视图快速获取索引使用、全表扫描、锁等待等诊断快照,并与前文原理对接。 - 模块5 以
EXPLAIN ANALYZE对比优化器估算与实际执行,并用performance_schema追踪底层等待事件与文件/表级 I/O。 - 模块6 整合前文原理(索引、锁、优化器、复制),给出四大典型故障的完整推演路径,包含详细命令和前后对比。
- 模块7 以 12 道面试高频题巩固并检验诊断思维,每题包含详细解释、至少三个追问与加分回答。
关键结论:慢查询诊断不是孤立的工具使用,而是从监控到日志、从指纹聚合到执行计划验证、从索引设计到锁分析的闭环过程。掌握五维诊断体系并结合前文核心原理进行因果分析,是数据库性能工程师的核心竞争力。
1. 慢查询日志机制与配置策略
1.1 日志字段详解
慢查询日志是性能诊断的一手数据源。当 log_output=FILE 时,每条记录包含多行开头为 # 的元数据行,后跟实际 SQL。以下是一个完整的记录示例:
sql
# Time: 2026-05-13T14:22:33.123456+08:00
# User@Host: app_user[app_user] @ [10.0.1.50] Id: 10742
# Query_time: 12.345678 Lock_time: 0.000123 Rows_sent: 5 Rows_examined: 1045828
# Thread_id: 10742 Errno: 0 Killed: 0 Bytes_received: 0 Bytes_sent: 1024
# Schema: order_db Last_errno: 0 Last_io_errno: 0
SET timestamp=1747117353;
SELECT order_id, customer_name FROM orders WHERE create_time > '2026-05-01' ORDER BY amount DESC LIMIT 5;
Query_time:精确到微秒,涵盖从语句开始执行到将结果发送给客户端的全部时间,包括锁等待(行锁、表锁、元数据锁)、I/O、CPU 计算、网络写入等。该值是判断语句是否慢的直接依据。Lock_time:语句在服务器层和存储引擎层等待锁的总时间。在 InnoDB 中,它包含表级意向锁等待、元数据锁等待以及行锁等待的开始阶段(但不含后续的锁等待休眠时间,因为行锁等待可能被中断并重试)。当Lock_time / Query_time比例异常时,必须排查锁争用,详细机制见第4篇。Rows_sent:实际返回给客户端的行数。它反映了结果集大小,但要注意返回大量行的查询本身可能带宽压力大,应结合业务判断。Rows_examined:语句执行过程中读取、评估的总行数,包括由索引扫描、全表扫描、回表、排序、临时表等产生的所有行读取。该值是性能瓶颈的核心标志:Rows_examined远大于Rows_sent意味着服务器做了大量无效工作,通常是由于无法使用索引、索引选择性差或查询需要额外的过滤。这直接关联第2篇的索引设计和第5篇的优化器统计。Thread_id:执行该语句的连接线程 ID,可在SHOW PROCESSLIST或performance_schema.threads中关联。Errno:语句执行时的错误号,0 表示正常。Killed标记表示语句是否被主动杀死。Bytes_received与Bytes_sent:客户端与服务器的传输字节数,可辅助判断网络开销。Schema:执行时所在的默认数据库,用于按业务库过滤。SET timestamp:Unix 时间戳,精确到秒。结合# Time可做时间序列分析。
当启用 log_slow_extra = ON(MySQL 8.0.14+)时,还会记录 Rows_affected、Bytes_sent、Bytes_received 等扩展字段,进一步丰富诊断维度。
1.2 核心参数配置与生产策略
生产环境应精细化配置,避免日志爆炸同时保留诊断能力:
| 参数 | 建议值 / 策略 | 说明 |
|---|---|---|
slow_query_log |
ON |
开启慢查询日志 |
long_query_time |
0.1 ~ 1.0 秒(依据 SLA) | 支持小数秒;对延迟敏感的业务可设 0.1s |
log_queries_not_using_indexes |
ON(必须配合限流参数) |
捕获全表扫描,必须设置 log_throttle_queries_not_using_indexes 和 min_examined_row_limit 避免日志风暴 |
log_throttle_queries_not_using_indexes |
10 ~ 60 条/分钟 | 限制每分钟记录未使用索引语句的数量,生产必备 |
min_examined_row_limit |
1000 ~ 10000 | 过滤那些未使用索引但扫描行数极少的"小查询",减少噪音 |
log_slow_admin_statements |
ON |
记录 OPTIMIZE TABLE、ALTER TABLE 等 DDL 管理语句 |
slow_query_log_always_write_time |
1.0 ~ 10.0 秒 | MySQL 8.0.14+,超过该绝对阈值的语句即使使用了索引也会强制记录 |
log_slow_extra |
ON(MySQL 8.0.14+) |
在日志中附加 Thread_id、Errno、Rows_affected 等扩展字段 |
slow_query_log_file |
独立磁盘或高性能路径 | 避免与数据目录争抢 I/O |
动态开启脚本示例(可在不重启 MySQL 的情况下生效):
sql
-- 开启慢查询日志,阈值0.5秒
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 0.5;
-- 记录未使用索引的查询,限流每分钟10条,且仅当扫描行数大于5000时
SET GLOBAL log_queries_not_using_indexes = ON;
SET GLOBAL log_throttle_queries_not_using_indexes = 10;
SET GLOBAL min_examined_row_limit = 5000;
-- 强制记录执行超过5秒的查询
SET GLOBAL slow_query_log_always_write_time = 5.0;
-- 启用扩展字段
SET GLOBAL log_slow_extra = ON;
注意:long_query_time 变更只对新连接生效,但通过 SET GLOBAL 更改后,已有连接会在下次执行语句时使用新的阈值(因为阈值检查发生在语句执行后)。
1.3 log_output 模式对比
| 模式 | 实现 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|---|
| FILE | 写入操作系统文件 | I/O 开销低,不影响事务引擎;文件可被轮替、压缩、离线分析 | 需要外部工具解析;多实例需分别管理 | 生产环境首选 |
| TABLE | 写入 mysql.slow_log 表(CSV 引擎,8.0 起支持转换为 InnoDB) |
可用 SQL 直接查询、过滤、聚合;无需文件权限 | 表写入增加存储引擎开销;表可能因为锁或磁盘满导致故障 | 仅临时诊断,或在无法获取文件时使用 |
| FILE,TABLE | 同时写入文件和表 | 具有两者优点 | 双重 I/O,浪费资源 | 不推荐 |
生产环境强烈推荐 FILE 模式,并配合 logrotate 或外部工具管理日志文件。如果临时需要分析,可以使用 pt-query-digest 直接读取文件。
1.4 五维诊断体系全景图
在进入工具链之前,先展示五维诊断体系的整体流转关系,揭示它们如何互补。
图表概述 :该图以慢查询日志为中心数据源,向上游对接 pt-query-digest 离线分析和 PMM 实时采集,向下游通过 sys schema 进行多维诊断,最终用 EXPLAIN ANALYZE 和 performance_schema 做精细验证,形成闭环。
核心元素解读:
pt-query-digest将日志指纹化,给出按总响应时间排序的 Profile,是定位头部问题 SQL 的利器。- PMM 的 Query Analytics 提供时间序列趋势,可回答"何时开始变慢"和"是偶发还是持续"。
sys schema的视图集则提供快捷的诊断快照,直接列出全表扫描、未使用索引、锁等待等,无需记忆底层表。EXPLAIN ANALYZE和performance_schema负责精细验证,定位到优化器偏差或具体等待事件。
诊断应用场景 :当 CPU 飙高时,可先从 PMM 查看 QPS 与慢查询趋势,然后用 pt-query-digest 分析对应时段日志,锁定 Top SQL,再通过 sys schema 和 EXPLAIN ANALYZE 找到根因(如全表扫描或统计信息过期)。
与前文知识关联:整个流程中,涉及索引缺失时调用第2篇,锁等待时调用第4篇,优化器统计信息错误时调用第5篇,主从延迟时调用第6篇。图中各诊断步骤会显式指向对应篇章。
2. pt-query-digest:查询指纹与响应时间分析
2.1 查询指纹算法深度解析
pt-query-digest 的查询指纹(Query Fingerprint)是将 SQL 文本归一化的核心算法,其设计目标是将功能相同的查询归为一类,即使具体字面量不同。算法步骤:
- 词法分析:基于 MySQL 的 SQL 解析器(来源于 DBD::mysql 或 MySQL 源码),将 SQL 字符串分解为 token 流,识别关键字、标识符、操作符、字面量等。
- 字面量替换 :将以下类型的字面量统一替换为
?:- 整数、浮点数(如
123,3.14) - 字符串常量(如
'active') - 日期/时间常量(如
'2026-05-01') - 二进制字面量(如
x'1A') - NULL 字面量(有些情况保留关键字 NULL,但具体数值值被替换)
- 列表中的多个值如
IN (1,2,3)会被替换为IN (?)。
- 整数、浮点数(如
- 空白标准化:将多余的空格、换行压缩为单个空格。
- 大小写统一:将 SQL 关键字和标识符转换为小写(或统一形式)。
- 计算摘要 :对标准化后的字符串计算 MD5 哈希,生成 16 字节的指纹 ID(如
0x59A3...)。
示例:
sql
SELECT * FROM orders WHERE customer_id = 1234 AND status = 'active';
指纹化后变为:
csharp
select * from orders where customer_id = ? and status = ?;
其哈希值为 0x...。所有具有相同指纹的 SQL 归为同一个"查询类",即使 customer_id 和 status 的值不同。
该指纹机制与 MySQL 的 performance_schema 中的 DIGEST 异曲同工。DIGEST 同样基于词法分析并归一化,但 pt-query-digest 完全脱离数据库运行,能处理历史慢查询日志、SHOW PROCESSLIST 实时采样、tcpdump 抓包等外部数据源,且统计维度更丰富(如提供 Variance-to-Mean Ratio 等)。
pt-query-digest 还提供了 --fingerprints 选项可单独输出每个查询的指纹,或通过 pt-fingerprint 工具快速获取一行 SQL 的指纹,方便脚本化分析。
2.2 Profile 与 Details 报告深度解读
运行 pt-query-digest slow.log 后,生成报告三大部分:Profile(概览)、Details(单个类详情)、Review(自动建议)。
Profile 概览
bash
# Profile
# Rank Query ID Response time Calls R/Call V/M Item
# ==== ================== ================ ====== ====== ===== ==================
# 1 0x59A3EF1... 11234.5678 67.8% 12345 0.9101 0.01 SELECT orders
# 2 0x8132B4C... 2345.6789 14.2% 2345 1.0005 0.02 UPDATE inventory
# 3 0xED4C9D2... 1234.5678 7.5% 3456 0.3573 0.00 SELECT customer
...
- Rank:按总响应时间(Response time)降序排列。通常 20% 的查询模板消耗了 80% 的总时间,聚焦前几个类即可获得最大优化收益。
- Response time:该类所有执行的总耗时,括号内为占整个日志总响应时间的百分比。该值直接反映资源消耗权重。
- Calls:执行次数。结合 R/Call 可判断是高频率小查询还是偶发的慢查询。
- R/Call:平均单次执行时间 = Response time / Calls。
- V/M (方差均值比,Variance-to-Mean Ratio):衡量该类查询执行时间的波动程度。
- V/M ≈ 0:执行时间非常稳定(可能只是简单的索引回表)。
- V/M 较大(如 > 1):执行时间抖动严重,意味着某些特定参数导致执行计划或数据量变化,可能某些执行非常慢。诊断时应重点关注 V/M 大的类,通过 Details 的直方图进一步分析。
- Item:指纹化后的 SQL 摘要,通常截断显示。
Details 详情(针对单个类)
shell
# Query 1: 0.16 QPS, 0.91x concurrency, ID 0x59A3... at byte 4567890
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.01, Time rank = 1
# Query_time distribution
# 1us
# 10us
# 100us
# 1ms
# 10ms #
# 100ms ############################################################
# 1s ##
# 10s+ #
# Tables:
# SHOW TABLE STATUS LIKE 'orders'\G
# SHOW CREATE TABLE `orders`\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT order_id, customer_name FROM orders WHERE create_time > '...' ORDER BY amount DESC LIMIT 5\G
- QPS 和 concurrency:该查询在统计时间窗口内的每秒调用次数和平均并发度(每秒活跃执行次数),用于评估是否突发流量导致。
- Query_time distribution:执行时间分布的直方图,采用对数尺度。上图显示绝大多数执行集中在 100ms 左右,但有少量执行达到 1s 甚至 10s+,这提示可能某些参数导致优化器选错索引或大量扫描。
- Tables :工具自动执行
SHOW TABLE STATUS和SHOW CREATE TABLE,帮助审查表结构和索引。 - EXPLAIN:工具使用模拟参数值尝试生成执行计划(结果可能不完全准确,但提供参考)。
Review 建议 :工具会在每个类下或报告末尾给出如 "The query has no WHERE clause"、"The table has no indexes"、"The query returned no rows but examined X rows" 等建议,辅助快速发现设计缺陷。
2.3 多种数据源采集
pt-query-digest 支持多种数据源,适用于不同场景:
-
慢查询日志文件 :最常用,直接指定文件或管道。
bashpt-query-digest /var/log/mysql/slow.log -
SHOW PROCESSLIST 实时采样 :适合无法开启慢日志或需要捕获当前瞬间问题。
bashpt-query-digest --processlist h=localhost --iterations 10 --run-time 60s -
tcpdump 网络包 :在无法接触数据库或需要分析网络层延迟时使用。
bashtcpdump -i eth0 port 3306 -w mysql.pcap pt-query-digest --type tcpdump mysql.pcap
2.4 pt-index-usage 索引使用分析
pt-index-usage 从慢查询日志或通用日志中提取所有 SQL,并结合数据库当前表结构,分析哪些索引被使用、哪些索引从未被使用,输出"可删除索引"候选列表。
bash
pt-index-usage slow.log --host=127.0.0.1 --user=root --password=xxx
输出示例:
sql
ALTER TABLE `order_db`.`orders` DROP INDEX `idx_create_time`, DROP INDEX `idx_customer_name`;
-- unused indexes
该分析基于日志中的查询计划,可能受限于日志内容。建议结合 sys.schema_unused_indexes 综合判断,并在删除前使用 INVISIBLE 索引进行验证(第2篇详述)。
2.5 查询指纹提取与 Profile 聚合流程图
图表概述 :该图描绘了 pt-query-digest 从多种数据源提取 SQL,经指纹归一化后聚合统计,最终生成 Profile 和详细报告的完整流水线。
核心元素解读:
- 数据源进入后,词法解析器是核心,它必须理解 MySQL SQL 语法以正确区分关键字与字面量。
- 指纹计算将结构相同的查询归为一类,压缩比可达 1000:1 以上,让分析聚焦。
- 统计信息中特别重要的是 V/M 值和时间分布直方图,它们揭示了查询的稳定性。
- 最终输出的 Profile、Details 和 Review 三层递进,从宏观到微观提供优化路径。
诊断应用场景 :当慢查询日志体积数 GB 时,直接阅读不可行;先用 pt-query-digest 生成 Profile,重点关注头部 3-5 个模板,查看它们的 V/M 和直方图,若发现 V/M 大且尾部有 10s+ 执行,再深入 Details 观察具体示例和 EXPLAIN。
与前文知识关联:Profile 中显示的时间分布异常需结合第5篇优化器统计信息分析;Details 中的索引建议需参考第2篇索引原理评估。
3. PMM 监控体系与指标关联分析
3.1 PMM 架构与数据采集原理
PMM(Percona Monitoring and Management)采用 Agent‑Server 架构:
- pmm‑client(pmm-agent) :运行在每个 MySQL 节点上,包含多个子模块:
node_exporter:采集操作系统指标(CPU、内存、磁盘、网络)。mysqld_exporter:通过 MySQL 连接采集SHOW STATUS、SHOW VARIABLES、performance_schema等几百个指标,对接 Prometheus。qan-agent(Query Analytics Agent):读取performance_schema.events_statements_summary_by_digest或慢查询日志,将查询指纹和统计数据推送到 PMM Server 的 Query Analytics (QAN) 组件。pt-heartbeat:定期在主库写入心跳记录,从库读取以精确计算复制延迟(优于Seconds_Behind_Master,关联第6篇)。
- pmm-server:核心为 Prometheus(时序存储)+ Grafana(可视化),并整合了 QAN API 和 ClickHouse 用于存储查询分析数据。数据保留由 Prometheus 和 ClickHouse 配置决定,默认 30 天。
pmm-agent 将慢查询日志的解析结果和 performance_schema 的 DIGEST 数据统一上报,使得 QAN 面板能够展示延迟分布、执行次数趋势、负载百分比,并可按数据库、用户、主机过滤。
3.2 核心面板的指标关联分析
MySQL Instance Summary
面板展示 QPS、TPS、连接数、InnoDB 行操作(读/写/删)、缓冲池命中率 (Buffer Pool hit ratio)等。当 QPS 平稳但慢查询速率突然上升,说明并非流量增大,而是某些 SQL 自身的执行效率下降。此时立即切换到 Query Analytics 面板。
MySQL Query Analytics (QAN)
QAN 是日常诊断的核心,它自动聚合所有查询指纹,并提供:
- 负载百分比 :每个查询消耗的总时间占所有查询总时间的比例,类似
pt-query-digest的 Profile,但是实时且时序化。 - 延迟分布:P50、P95、P99 等百分位延迟曲线,可直接看某个查询的延迟是何时开始恶化。
- 执行次数趋势:如果次数不变但延迟增加,意味着查询内部执行效率下降;如果次数陡增,可能是应用侧流量变化。
- 与
pt-query-digest的区别 :QAN 提供时间序列监控 ,适合看趋势和回溯历史;pt-query-digest提供静态详细统计 和直方图、Review 建议。两者互补,先在 QAN 发现异常时段,再用pt-query-digest对该时段日志深度分析。
MySQL InnoDB Details
- Buffer Pool Hit Ratio:命中率骤然下降意味着大量磁盘读,可能由新出现的全表扫描或 Buffer Pool 太小导致。结合 QAN 中突然出现的全表扫描查询,可定位元凶。
- InnoDB Row Lock Wait Time / Count :锁等待指标飙升,往往伴随
Lock_time增高的慢查询。此时应使用sys.innodb_lock_waits查看当前阻塞链,并从慢查询日志中提取Lock_time高的 SQL,关联第4篇加锁规则分析是索引缺失导致间隙锁扩大还是事务未及时提交。
MySQL Replication
- Replication Lag 趋势:若主从延迟突然增大,检查同一时刻主库的 Binlog 生成速率和网络流量。可能是大事务或批量 DML 导致从库单线程回放落后。结合第6篇
WRITESET并行复制,通过performance_schema.replication_applier_status_by_worker诊断延迟 Worker 是否因大事务无法分发。
关联分析思路:当多个面板同时报警,可按因果链排查:慢查询增多 → 全表扫描 → Buffer Pool 脏页增多、命中率下降 → 磁盘 I/O 上升 → 锁持有时间变长 → 锁等待增加 → 可能影响主库吞吐,进而导致从库延迟。PMM 将这些分散的指标统一在同一时间轴下,通过 Grafana 的联动可迅速关联。
3.3 告警规则设计(PrometheusRule)
生产环境应配置主动告警。以下为 PrometheusRule 示例:
yaml
groups:
- name: mysql_slow_queries
rules:
- alert: MySQLHighSlowQueryRate
expr: rate(mysql_global_status_slow_queries[5m]) > 10
for: 5m
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.instance }} 慢查询速率 >10/s"
- alert: MySQLBufferPoolHitRatioLow
expr: mysql_global_status_innodb_buffer_pool_read_requests / (mysql_global_status_innodb_buffer_pool_read_requests + mysql_global_status_innodb_buffer_pool_reads) < 0.95
for: 5m
labels:
severity: critical
annotations:
summary: "Buffer Pool 命中率低于 95%"
- alert: MySQLReplicationLag
expr: mysql_slave_status_seconds_behind_master > 10
for: 2m
labels:
severity: critical
annotations:
summary: "主从延迟超过 10 秒"
- alert: MySQLHighLockWaitTime
expr: rate(mysql_global_status_innodb_row_lock_time[5m]) > 5000
for: 5m
labels:
severity: warning
annotations:
summary: "InnoDB 行锁等待时间增长率 >5ms/秒"
3.4 PMM 核心监控指标关联分析图
图表概述:该因果链从宏观 QPS 到慢查询、全表扫描、I/O、锁等待,最终扩散到复制延迟,展示了典型性能雪崩的传递路径。
核心元素解读:
- 慢查询增加往往是全表扫描、无效索引或统计信息过期所致。
- 全表扫描将大量数据页读入 Buffer Pool,导致热点页被挤出,命中率下降,产生大量磁盘读。
- 磁盘 I/O 加重会拖慢所有语句的执行,锁持有时间变长,进而引发锁等待堆积。
- 锁等待与 I/O 抖动会影响主库的 Binlog 写入吞吐,结合大事务可能加剧从库延迟。
诊断应用场景:当 PMM 仪表盘同时出现命中率下降、锁等待增加和复制延迟时,首先检查 QAN 中是否出现新的高负载查询,然后沿此链路逐一验证。
与前文知识关联:锁等待分析深入依赖第4篇;优化器选择错误导致全表扫描需参考第5篇;主从延迟原因见第6篇。
4. sys schema 性能诊断视图集
sys schema 将 performance_schema 的复杂表关系封装为 20+ 常用视图,以下按功能维度分类讲解,每类给出核心视图、诊断价值、SQL 示例及与前文知识的显式关联。
4.1 索引分析类
核心视图:
schema_unused_indexes:列出自性能模式启用以来从未被查询使用过的索引。其底层基于performance_schema.table_io_waits_summary_by_index_usage,统计索引上的读次数。schema_index_statistics:每个索引的读写次数、读取行数、插入/更新/删除行数等,可判断索引读多写少还是写多读少。schema_redundant_indexes:检测重复索引或前缀冗余(如已有(A, B),又有(A)),前者可视为冗余。
诊断价值 :删除未使用和冗余索引可降低写入操作的 B+Tree 维护开销,减少页分裂,提高写性能(详见第2篇)。schema_index_statistics 中的 rows_selected 与 rows_inserted 等可帮助判断索引是否值得保留。
SQL 示例:
sql
-- 查看 order_db 中未使用的索引
SELECT object_schema, object_name, index_name
FROM sys.schema_unused_indexes
WHERE object_schema = 'order_db';
输出:
diff
+---------------+-------------+-------------------+
| object_schema | object_name | index_name |
+---------------+-------------+-------------------+
| order_db | orders | idx_create_time |
| order_db | orders | idx_customer_name |
+---------------+-------------+-------------------+
若确认这些索引已不再被任何查询使用,可先将其设为 INVISIBLE 观察,再删除。
4.2 查询分析类
核心视图:
statement_analysis:直接来源于performance_schema.events_statements_summary_by_digest,提供标准化后的查询总耗时、锁等待时间、扫描行数、返回行数、临时表使用、全表扫描等综合指标。statements_with_temp_tables:使用了磁盘临时表的查询(对应Using temporary),往往因ORDER BY或GROUP BY无法使用索引造成。statements_with_full_table_scans:执行全表扫描的查询,直接指向索引缺失或失效。statements_with_sorting:使用文件排序(Using filesort)的查询,如果排序行数大,会消耗大量 CPU 和内存。
诊断价值 :statement_analysis 可视为内置的 pt-query-digest 概要,用于实时获取高消耗 SQL。全表扫描和临时表查询需重点关注,前者往往因索引失效,后者可能因 ORDER BY 与 GROUP BY 未使用索引(关联第2篇索引排序优化)。
SQL 示例:
sql
SELECT query, exec_count, total_latency, rows_sent_avg, rows_examined_avg,
tmp_disk_tables, full_scan
FROM sys.statement_analysis
WHERE db = 'order_db'
ORDER BY total_latency DESC LIMIT 5;
输出各字段的含义一目了然,rows_examined_avg 远大于 rows_sent_avg 是典型扫描放大。
4.3 I/O 与磁盘分析类
io_global_by_file_by_bytes:按文件显示读写字节数,可定位哪些数据文件是 I/O 热点(如order_db/orders.ibd)。io_global_by_wait_by_latency:按等待时间展示 I/O 操作,识别慢 I/O 子系统。
配合 schema_tables_with_full_table_scans:当热点文件的表出现在全表扫描列表中时,即可确认扫描导致了大量磁盘读。
4.4 内存分析类
memory_global_by_current_bytes:按内存子系统(如 InnoDB buffer pool、key cache、内部临时表)显示当前内存分配,可用于发现内存泄漏或大内存消费者。memory_by_thread_by_current_bytes:每个连接线程的内存占用,排查某些会话异常增长。
4.5 锁与等待分析类
innodb_lock_waits:显示当前 InnoDB 事务之间的锁等待链,包括等待事务的 SQL、阻塞事务的 SQL 及锁类型。关联第4篇可推断为何产生锁等待(如间隙锁导致)。schema_table_lock_waits:元数据锁等待(常见于 DDL 与 DML 冲突)。
示例:
sql
SELECT waiting_trx_id, waiting_pid, waiting_query,
blocking_trx_id, blocking_pid, blocking_query
FROM sys.innodb_lock_waits;
若发现大量 UPDATE 等待同一个事务的 S 锁,需依据第4篇加锁规则分析该事务是否未提交或持有过多范围锁。
4.6 复制分析类
replication_status:展示当前从库复制状态,包括Seconds_Behind_Master、GTID 相关、Master_Log_File等。replication_applier_status_by_worker:多线程复制下每个 Worker 的延迟统计,关联第6篇并行复制。
4.7 sys schema 诊断视图分类图
图表概述 :此图将 sys schema 的 20+ 视图归纳为六大类,每类下列出代表性视图,构成快速诊断的"索引卡",无需记忆底层 performance_schema 表。
核心元素解读:
- 索引分析类用于评估索引有效性,消除冗余。
- 查询分析类直接指出高消耗 SQL 及其伴随症状(全表扫描、临时表、文件排序),是性能快照的第一入口。
- I/O 与磁盘类揭示物理资源热点,与查询分析类联合使用可定位"哪个查询导致哪个文件 I/O 高"。
- 内存、锁、复制视图则分别覆盖其他关键资源维度。
诊断应用场景 :面对一个未知的性能问题,首先查询 statement_analysis 定位 Top SQL,再根据其症状查阅对应分类:如果是全表扫描则看索引分析类是否缺失索引;如果锁等待多则直接查 innodb_lock_waits。整个检查过程无需记忆复杂的 performance_schema 细节。
与前文知识关联:索引分析类与第2篇密切相关;锁等待类直接应用第4篇的加锁模型;复制类可结合第6篇的并行复制参数进行调优。
5. EXPLAIN ANALYZE 与 performance_schema 精细诊断
5.1 EXPLAIN ANALYZE:实际执行与估算的对比
EXPLAIN ANALYZE(MySQL 8.0.18+)不仅显示优化器的预估执行计划,还实际执行语句(对 InnoDB 最后回滚),输出以迭代器树 形式组织,每个节点包含 actual time、actual rows 和 loops。
输出解读:
sql
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42 AND status = 'pending';
可能的输出:
sql
-> Filter: ((orders.status = 'pending')) (cost=1000.00 rows=100) (actual time=5.123..5.456 rows=2 loops=1)
-> Index lookup on orders using idx_customer_id (customer_id=42) (cost=500.00 rows=5000) (actual time=0.234..4.789 rows=5000 loops=1)
cost和rows:优化器估算的代价和行数。actual time:格式start..end(毫秒),start是返回第一行的时间(启动时间),end是返回所有行的总时间。两者差值大说明需要处理大量行后才能返回(如排序、分组)。actual rows:该迭代器实际返回的行数。如果某节点actual rows远大于其父节点或估算rows,则是性能瓶颈------大量行被读取后丢弃。loops:该迭代器被循环执行的次数(Nested Loop Join 中内部表会多次扫描)。
对比诊断方法:
- 定位
actual rows最大的叶子节点(最内层),通常是索引扫描或全表扫描。 - 检查该节点的
actual rows与估算rows的差异。若差异巨大,说明统计信息失真,需ANALYZE TABLE或调整innodb_stats_persistent_sample_pages(第5篇)。 - 如果估算正确但实际行数仍大,说明查询条件本身选择性差,需要改索引。
- 观察
actual time的启动时间,若外层Filter的启动时间很长,意味着内层输出慢。
迭代器树纵深解读:
Table scan:全表扫描,actual rows等于表行数,非常危险。Index lookup:索引等值查找或范围扫描,通过索引减少扫描。Index range scan:范围扫描,actual rows由条件决定。Nested loop inner join:内表被多次扫描,loops值等于外表行数。Filter:对子节点输出的行进行条件过滤,若过滤后行数骤降,应考虑将条件下推到索引(如索引覆盖)。
5.2 与第5篇优化器估算模型的衔接
当 actual rows 与估算 rows 差异巨大,且 ANALYZE TABLE 无法解决时,可能是优化器代价常数不匹配(如 random_page_cost 过高)或连接顺序不当。此时需参考第5篇中对 optimizer_switch、直方图统计、Histogram 的分析,可能需要手动调整 SET optimizer_switch='...' 或使用 STRAIGHT_JOIN。
5.3 performance_schema 精细诊断
performance_schema 提供底层的等待事件、文件 I/O、表 I/O 统计,是连接 SQL 行为与系统资源的桥梁。
5.3.1 events_statements_summary_by_digest
为所有执行过的 SQL 指纹提供聚合统计,与 sys.statement_analysis 同源,但可直接按需查询:
sql
SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT, SUM_LOCK_TIME,
SUM_ROWS_EXAMINED, SUM_ROWS_SENT
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME = 'order_db'
ORDER BY SUM_TIMER_WAIT DESC LIMIT 5;
该表数据会随新 SQL 执行而更新或淘汰(表大小由 performance_schema_max_digest_length 和 performance_schema_digests_size 控制),适合实时分析。
5.3.2 events_waits_current / history / summary
events_waits_current:每个线程当前正在等待的事件(如等待 I/O、等待锁、等待 CPU)。events_waits_summary_global_by_event_name:全局等待事件汇总,按事件名称统计总等待次数和时间。- 等待事件分类 :
wait/io/file/innodb/innodb_data_file:读取 InnoDB 数据文件,I/O 瓶颈的重要标志。wait/synch/mutex/innodb/buf_pool_mutex:Buffer Pool 的互斥锁竞争,说明并发访问 Buffer Pool 激烈。wait/synch/mutex/innodb/trx_sys_mutex:事务系统互斥锁,可能因大量活动事务。wait/lock/metadata/sql/mdl:元数据锁等待,通常由 DDL 与 DML 冲突引起。
通过查询 events_waits_summary_global_by_event_name 的 SUM_TIMER_WAIT,可快速定位全局最耗时的等待类型,进而关联到具体的 SQL。
5.3.3 file_summary_by_instance
按文件(数据文件、日志文件等)展示 I/O 次数、读写字节和等待时间,用于定位 I/O 热点文件,与 sys.io_global_by_file_by_bytes 功能相似,但提供更细的延迟分布。
5.3.4 table_io_waits_summary_by_table
按表统计读/写/插入/删除等操作的 I/O 等待,识别热点表。例如某表 COUNT_READ 极高且 SUM_TIMER_READ 大,说明大量查询读取该表。
与 sys schema 联动 :sys.io_global_by_file_by_bytes 和 table_io_waits_summary_by_table 可共同确认热点表,然后通过 statement_analysis 找到访问该表的查询,最后用 EXPLAIN ANALYZE 验证。
6. 系统化排查决策树与场景推演
6.1 四场景排查决策树总图
以下决策树整合了前文所有工具与前文原理(索引、锁、优化器、复制),给出四大典型故障的标准化诊断路径。
图表概述:决策树将四大场景的排查路径结构化,每个分支都明确指定了使用的工具和诊断检查点,并显式引用了前文相关章节,形成可操作的"故障手册"。
核心元素解读:
- 所有路径都遵循"宏观确认 → 指纹/统计聚焦 → 执行计划/锁分析验证 → 原理指导修复 → 效果验证"的闭环。
- 在 CPU/慢查询分支,特别区分了 V/M 小(整体慢)和 V/M 大(抖动慢)两种模式,引导不同的分析方向。
- 在锁等待分支,强调先解决阻塞源(可能是长事务或缺失索引),而非被阻塞的查询。
- 在 I/O 和复制分支,将热点文件、全表扫描、大事务等关键因素串联。
诊断应用场景:当实际遇到问题,可沿对应分支执行,每一步都有具体的命令或视图,避免"猜测式"优化。
与前文知识关联:图中几乎每个判断框都标注了需参考的篇章,确保读者能将诊断实践与理论基础牢牢绑定。
6.2 完整诊断案例推演(CPU 飙高场景)
现象:线上数据库 CPU 使用率从 30% 突然飙升至 95%,慢查询日志速率从 2/s 暴涨至 50/s。
1. 宏观确认(PMM)
- 打开 PMM
MySQL Instance Summary,观察到 QPS 保持 2000/s 未变,TPS 略有下降,连接数无异常。排除流量突增。 - 切换到
MySQL Query Analytics,发现一个模板SELECT * FROM orders WHERE customer_id = ? AND create_time BETWEEN ? AND ?的负载百分比从 5% 跃升至 70%,延迟中位数从 5ms 上升到 800ms。同时该查询的扫描行数均值从 1000 变为 200 万。
2. 离线聚焦(pt-query-digest) 导出近 15 分钟慢查询日志进行分析:
bash
pt-query-digest --since '15m' /var/log/mysql/slow.log > report.txt
Profile 显示该查询(指纹 0xBF4D...)总响应时间占比 68%,V/M=0.03 较小,说明整体执行都慢。直方图显示绝大多数执行在 500ms~2s 之间。Details 中的示例 SQL:
sql
SELECT * FROM orders WHERE customer_id = 42 AND create_time BETWEEN '2026-05-01' AND '2026-05-13';
Rows_examined 约 200 万,Rows_sent 仅 10。
3. 诊断快照(sys schema)
sql
SELECT * FROM sys.statement_analysis
WHERE query LIKE '%orders%' AND db='order_db'
ORDER BY total_latency DESC LIMIT 1;
确认该查询 full_scan = 1,tmp_disk_tables = 1。同时检查 schema_unused_indexes,发现存在 idx_customer_id 但未被该查询使用(因为还有一个范围条件 create_time,优化器可能认为需要回表过滤,且 create_time 选择性较高时不使用复合索引)。
4. 执行计划验证(EXPLAIN ANALYZE)
sql
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42 AND create_time BETWEEN '2026-05-01' AND '2026-05-13';
输出:
sql
-> Filter: (orders.create_time between '2026-05-01' and '2026-05-13') (cost=20000.00 rows=200000) (actual time=800.123..1500.456 rows=10 loops=1)
-> Index lookup on orders using idx_customer_id (customer_id=42) (cost=5000.00 rows=300000) (actual time=10.234..1200.789 rows=300000 loops=1)
可见优化器使用 idx_customer_id 扫描了该客户的全部 30 万行,然后回表过滤 create_time,最终只返回 10 行。执行时间主要消耗在回表和过滤上。估算 rows=300000 与实际一致,说明统计信息准确,但索引设计未覆盖 create_time 条件。
5. 根因与优化(第2篇索引原理) 创建一个复合索引以支持等值+范围查询:
sql
ALTER TABLE orders ADD INDEX idx_cust_ctime (customer_id, create_time);
新索引使得条件 customer_id = 42 AND create_time BETWEEN ... 能直接在索引中执行范围扫描并过滤,无需回表即可获得 create_time,且 SELECT * 仍需回表,但范围扫出的行数大大减少(因为 create_time 参与索引过滤)。再次 EXPLAIN ANALYZE:
sql
-> Index range scan on orders using idx_cust_ctime, with index condition: (customer_id = 42) and (create_time between '2026-05-01' and '2026-05-13') (cost=500.00 rows=50) (actual time=0.500..1.200 rows=10 loops=1)
-> Table lookup on orders (cost=100.00 rows=10) (actual time=0.100..0.300 rows=10 loops=1)
actual rows 从 300,000 骤降至 10,执行时间降到 1.5ms。
6. 效果验证 PMM MySQL Query Analytics 中该查询延迟恢复到 5ms,CPU 使用率降至 35%,慢查询速率恢复正常。优化完成。
7. 面试高频专题
(本节与前文正文分离,独立成体系,涵盖 12 道典型面试题,每题包含一句话回答、详细解释、至少三个追问与加分回答。)
7.1 慢查询日志中 Rows_examined 远大于 Rows_sent 意味着什么?如何优化?
一句话回答:意味着查询扫描了大量行但最终只返回少量数据,通常是因为索引失效、索引选择性差或无法使用索引覆盖。
详细解释 : Rows_examined 是存储引擎层为执行查询而检查的总行数,Rows_sent 是返回给客户端的行数。当两者相差巨大时,说明引擎做了大量无效的过滤工作。可能原因:
- 没有使用索引:导致全表扫描。
- 索引选择性低 :即使使用索引,但因为索引列过滤能力弱(如
status只有三个值),仍会扫描大量行。 - 需要回表过滤 :索引覆盖不完整,需要回主表读取行再用
WHERE筛选,导致扫描放大。 - 统计信息过期:优化器低估了行数,选择了错误的索引(关联第5篇)。 优化手段包括:添加覆盖索引、创建复合索引以支持所有过滤条件、使用索引条件下推(ICP)或 MRR 减少回表等,详见第2篇索引原理。
追问1 :如果 EXPLAIN 显示使用了索引,但 Rows_examined 仍很大,是什么原因? 答:通常是因为索引选择性差,比如 WHERE gender = 'M' 在男女各半的表中,扫描全表一半的行;或者使用了范围查询但范围很大。也可能是优化器因统计信息过期而选择了错误的索引。
追问2 :如何利用 performance_schema 持续监控 Rows_examined / Rows_sent 比率? 答:通过 events_statements_summary_by_digest 查询 SUM_ROWS_EXAMINED 和 SUM_ROWS_SENT,计算比率并定时快照,建立基线。sys.statement_analysis 也直接提供 rows_examined_avg 和 rows_sent_avg。
追问3:索引覆盖一定能解决扫描放大问题吗? 答:索引覆盖可避免回表,但如果索引本身仍需扫描大量行(因选择性差),则扫描放大依然存在,只是减少了回表 I/O。仍需优化索引列顺序或改写查询。
加分回答 :使用 EXPLAIN ANALYZE 的 actual rows 可以准确定位哪个迭代器产生了行放大;结合 pt-query-digest 的直方图可观察哪些参数导致扫描量暴增。此外,有时可考虑使用延迟关联(deferred join)技术,先通过覆盖索引快速定位主键,再回表取数据,以减少扫描行数。
7.2 pt-query-digest 的查询指纹是如何工作的?Profile 报告如何解读?
一句话回答 :通过词法分析将 SQL 中的字面量替换为占位符 ?,从而将结构相同的查询归为一类,Profile 按总响应时间排序展示各类的资源消耗占比。
详细解释 : pt-query-digest 内置轻量级 SQL 解析器,能识别数字、字符串、日期等字面量并将其替换为 ?,保留对象名和结构。随后按指纹分组聚合,统计每个类的总耗时、调用次数、时间分布等。Profile 报告的核心是 Response time 列和百分比,聚焦前几个类即可获得最大优化收益。V/M(方差均值比)反映执行时间的稳定性,若很大则说明部分执行特别慢,需查看直方图进一步分析。
追问1 :如果 Profile 中排名第一的类 V/M 非常大,该如何分析? 答:说明该类查询时间波动剧烈,可能某些参数导致全表扫描或连接顺序改变。应使用 pt-query-digest 的 --output=slowlog 将该类原始 SQL 提取出来,对慢的执行单独 EXPLAIN,或通过 EXPLAIN ANALYZE 模拟不同参数。
追问2 :pt-query-digest 与 performance_schema 的 DIGEST 有何异同? 答:原理类似,但 pt-query-digest 可处理离线日志、tcpdump 等外部数据源,统计维度更多(V/M、直方图、Review);而 DIGEST 基于内存,实时但可能因表大小限制丢失旧数据。两者互补,DIGEST 适合实时监控,pt-query-digest 适合历史深度分析。
追问3 :如何用 pt-query-digest 对比两个不同时间段的慢查询情况? 答:可以分别分析两个日志文件,然后用 --compare 选项对比,或分别生成报告后 diff。还可以使用 --since 和 --until 选项截取时间段。
加分回答 :pt-query-digest 支持 --filter 参数,可编写 Perl 表达式过滤特定查询,灵活度高。同时,可结合 pt-index-usage 对同一份日志分析索引使用情况,形成联合优化建议。
7.3 PMM 的 Query Analytics 面板如何帮助定位慢查询?它和 pt-query-digest 有什么区别?
一句话回答 :QAN 实时展示查询指纹的延迟分布、频率和负载趋势,便于发现性能退化;pt-query-digest 是离线深度分析工具,提供更详细的统计摘要和优化建议。
详细解释 : PMM 的 QAN 通过 pmm-agent 读取 performance_schema 的 DIGEST 数据或慢查询日志,将查询指纹的时间序列数据存入 ClickHouse,从而支持:
- 按时间段查看每个查询的负载百分比、P95 延迟、执行次数。
- 钻取到单个查询的
EXPLAIN计划、表信息。 - 回溯历史,回答"这个查询从哪天开始变慢?"。 相比之下,
pt-query-digest对慢查询日志做一次性的全面剖析,提供精确的直方图、V/M 和 Review 建议,但不能直接做时间序列趋势分析。两者配合使用:QAN 发现异常,pt-query-digest深入诊断。
追问1 :QAN 中如果某查询延迟突然增大,但执行次数不变,可能是什么原因? 答:可能是数据量增长导致扫描行数增加,统计信息过期使优化器选错索引,锁争用加剧,或系统资源(I/O、CPU)被其他作业抢占。需结合 InnoDB Details 和 EXPLAIN 分析。
追问2:PMM 的 QAN 数据可以保留多久?能否自己配置? 答:默认保留 30 天(ClickHouse 设置),可通过修改 pmm-server 的 ClickHouse TTL 配置延长,但会占用更多磁盘。
追问3 :QAN 中 Query_time 分布是如何计算的? 答:它是基于 performance_schema 中每个 DIGEST 的统计信息,结合直方图数据(events_statements_histogram_by_digest)生成的,可展示 P50、P95、P99 等百分位。
加分回答 :PMM 还提供 Query Response Time 面板,基于 performance_schema 的响应时间直方图,可分析全局查询延迟分布。QAN 还支持按数据库、用户、主机过滤,并能设置"查询模板忽略值大小写"等选项。
7.4 sys.schema_unused_indexes 和 sys.schema_index_statistics 的区别是什么?如何用它们做出索引删除决策?
一句话回答 :schema_unused_indexes 直接列出从未被使用的索引,是删除候选;schema_index_statistics 显示每个索引的读写次数、影响行数等,辅助判断索引是否值得保留。
详细解释 : schema_unused_indexes 基于 table_io_waits_summary_by_index_usage,查找自数据库启动或 performance_schema 重置以来没有任何读操作的索引。但需注意统计周期可能不够长,某些业务低频查询尚未触发。 schema_index_statistics 则提供每个索引的 rows_selected、rows_inserted、rows_updated、rows_deleted 等,可计算读写比。若某个索引读操作极少而写操作频繁,即使偶有使用,也可考虑删除以降低写入开销。
索引删除决策流程:
- 从
schema_unused_indexes获取初步候选列表。 - 将候选索引通过
schema_index_statistics确认读写统计,排除确实有低频读的索引。 - 使用
pt-index-usage分析历史慢日志进一步验证。 - 在备库或非高峰时段先执行
ALTER TABLE ... ALTER INDEX idx_name INVISIBLE(MySQL 8.0),观察业务是否受影响。 - 确认无影响后执行
DROP INDEX。
追问1 :为什么不能仅根据 schema_unused_indexes 就删除索引? 答:因为统计可能从最近一次重启才开始,或者 performance_schema 被清空过,导致未记录到某些周期性的查询(如月报表)。此外,某些索引可能用于外键约束或唯一约束,删除会破坏完整性。
追问2 :如何安全地验证一个索引是否真的没用? 答:将其设为 INVISIBLE(优化器不可见),观察一段完整业务周期(如一周),期间检查 schema_unused_indexes 是否仍然未使用,以及应用层是否出现性能下降或报错。
追问3:删除索引对写入性能到底有多大提升? 答:每个二级索引的插入/更新都需要维护 B+Tree,包括页分裂、日志写入。在高写入场景下,删除一个冗余索引可以降低 10%~30% 的写入延迟(视索引大小和数据量而定),原理见第2篇。
加分回答 :结合 sys.schema_redundant_indexes,识别重复索引(如 (A,B) 和 (A)),优先删除冗余前缀索引。同时监控 Innodb_pages_written 等指标验证删除后的写入优化效果。
7.5 EXPLAIN ANALYZE 与 EXPLAIN 的根本区别?actual time 和 actual rows 对诊断有什么帮助?
一句话回答 :EXPLAIN 显示优化器的估算计划,EXPLAIN ANALYZE 实际执行并输出每个迭代器的真实耗时和行数。
详细解释 : EXPLAIN 完全基于统计信息和代价模型生成计划,不实际执行;EXPLAIN ANALYZE 会执行语句(对 InnoDB 自动回滚),测量每个迭代器的实际启动时间、总时间和返回行数。两者对比可以揭示:
- 统计信息是否准确(估算
rowsvsactual rows)。 - 优化器代价模型是否合理(估算的
cost与实际耗时)。 - 执行计划的瓶颈节点在哪里(哪个迭代器
actual time高或actual rows大)。actual time=0.1..5.2中,第一个数字是返回第一行的启动时间,第二个数字是总执行时间。差值大说明需要处理大量行后才能返回(如文件排序)。
追问1 :如果 actual rows 远大于 rows 估算值,该如何处理? 答:首先执行 ANALYZE TABLE 更新统计信息;若仍不准,可能需要增加采样页数 innodb_stats_persistent_sample_pages 或启用直方图。也可通过 SET optimizer_switch 调整连接顺序或索引选择。详见第5篇。
追问2 :EXPLAIN ANALYZE 会修改数据吗?生产环境能用吗? 答:对于 InnoDB,语句执行后会自动回滚,不会持久化修改。但对于 DML 会影响自增 ID 等(即使回滚也不会回收),应在低峰期或测试环境执行,或使用 BEGIN; EXPLAIN ANALYZE ...; ROLLBACK; 显式控制。
追问3 :如何解读 Nested loop inner join 中的 actual rows 和 loops? 答:loops 表示内部表被扫描的次数(通常等于外表行数),actual rows 是每次循环返回的行数。若 actual rows 很大且 loops 也大,则产生笛卡尔积效应,需优化连接条件或添加索引。
加分回答 :结合 EXPLAIN FORMAT=JSON 与 EXPLAIN ANALYZE,可以得到代价详情和实际测量值,用于分析优化器的成本计算是否准确,进而调整 server_cost 或 engine_cost 表中的常数。
7.6 sys.statement_analysis 的数据来源是什么?与 performance_schema.events_statements_summary_by_digest 是什么关系?
一句话回答 :数据直接来源于 performance_schema.events_statements_summary_by_digest,sys.statement_analysis 是对它的格式化视图,增加了人性化列和聚合表达。
详细解释 : sys.statement_analysis 通过查询 performance_schema.events_statements_summary_by_digest 并连接其他表(如 sys.x$ps_digest_avg_latency_distribution)生成,它将 timer_wait 转换为人类可读的 total_latency,提供 rows_sent_avg、rows_examined_avg、tmp_disk_tables、full_scan 等字段。用户无需关心底层 performance_schema 的复杂关联。可通过 SHOW CREATE VIEW sys.statement_analysis 查看其具体 SQL。
追问1 :为什么 sys.statement_analysis 中有时看不到最近执行的 SQL? 答:因为 events_statements_summary_by_digest 表有固定大小(由 performance_schema_digests_size 控制),当新 SQL 挤满表时,最旧的条目会被移除以腾出空间。可适当增大该参数。
追问2 :sys.statement_analysis 中的 total_latency 是包含锁等待时间吗? 答:是的,total_latency 对应 SUM_TIMER_WAIT,包含了执行期间的所有等待(CPU、I/O、锁等待等)。同时视图也提供了 lock_latency 列单独展示锁等待时间。
追问3 :如何基于 sys.statement_analysis 建立 SQL 性能基线? 答:可定期(如每小时)执行 SELECT * FROM sys.statement_analysis 并存入带时间戳的基线表,通过对比不同时段的 total_latency 和 rows_examined_avg 发现性能退化。
加分回答 :sys schema 还提供 x$statement_analysis,其时间值以皮秒为单位,便于程序处理。可以编写脚本定期采集 x$statement_analysis 到外部监控库,实现长期趋势分析。
7.7 如何通过 sys.innodb_lock_waits 分析当前的锁等待情况?如何关联第4篇的加锁规则?
一句话回答 :sys.innodb_lock_waits 显示当前被阻塞的事务和持有锁的事务,结合 SHOW ENGINE INNODB STATUS 的锁信息,可回溯到第4篇的 Record Lock、Gap Lock、Next‑Key Lock 规则进行判断。
详细解释 : 视图提供 waiting_trx_id、waiting_query、blocking_trx_id、blocking_query、locked_table、locked_index 等字段。若等待的锁类型是 RECORD,且被阻塞查询使用了正确的索引,但阻塞事务却持有间隙锁(因非唯一索引范围查询),就可以根据第4篇的加锁规则分析出为何间隙锁冲突。例如,一个 SELECT ... FOR UPDATE 使用非唯一索引 WHERE status = 'active',会锁定所有匹配行的 Next‑Key Lock,导致其他插入或更新被阻塞。
追问1 :如果 innodb_lock_waits 显示等待同一行记录,且阻塞事务处于空闲状态,怎么处理? 答:阻塞事务可能是开启了忘记提交的应用事务,应通过 KILL 命令终止该连接,并排查应用代码逻辑,确保事务及时提交。
追问2 :如何查看某个事务持有的具体锁? 答:在 MySQL 8.0 中,使用 performance_schema.data_locks 表查看锁的详细信息(锁模式、锁定行),结合 data_lock_waits 了解阻塞关系。早期版本可用 information_schema.INNODB_LOCKS(已弃用)。
追问3 :锁等待与慢查询日志中的 Lock_time 字段是什么关系? 答:Lock_time 记录的是该语句整个执行过程中花费在锁等待上的总时间,是一个聚合值;而 innodb_lock_waits 是瞬时的阻塞链。两者结合:高 Lock_time 的语句可关联到当时的锁等待视图(如果抓取到)以定位阻塞源。
加分回答 :使用 pt-deadlock-logger 定时抓取 SHOW ENGINE INNODB STATUS 中的死锁信息并记录,有助于分析周期性死锁。结合第4篇的死锁分析,优化加锁顺序。
7.8 Buffer Pool 命中率突然下降,可能的原因有哪些?需要查看哪些 sys schema 视图?
一句话回答 :可能原因包括突然出现大量全表扫描、统计数据被清空、Buffer Pool 被大查询冲刷或设置过小;需查看 sys.statements_with_full_table_scans、io_global_by_file_by_bytes、schema_index_statistics 等。
详细解释: Buffer Pool 命中率下降意味着逻辑读减少,物理读增加。常见诱因:
- 全表扫描:将整个表的数据页读入 Buffer Pool,挤走热数据。
- 大范围索引扫描:虽然用了索引,但范围很大,同样加载大量页。
- 备份或导出 :如
mysqldump不加--quick可能导致大查询。 - Buffer Pool 太小 :正常业务数据量增长,热数据超过 Buffer Pool 容量。 诊断时先用
sys.statements_with_full_table_scans找到全表扫描查询;用io_global_by_file_by_bytes找出读取量最大的文件,再用schema_index_statistics查看相关表的索引使用情况。
追问1:如果命中率下降但 QPS 变化不大呢? 答:可能只是某些后台操作(如定期统计收集、备份)引起,但仍需确认是否有新的全表扫描查询出现。
追问2 :如何恢复命中率? 答:最直接的方法是优化导致全表扫描的查询,增加合适索引;若物理内存允许,适当调大 innodb_buffer_pool_size;还可考虑调整 Buffer Pool 驱逐策略(如 innodb_old_blocks_pct)。
追问3 :sys.memory_global_by_current_bytes 在这里有用吗? 答:可以查看 InnoDB buffer pool 实际分配的内存(memory/innodb/buf_buf_pool),确认是否与配置一致,以及是否有其他内存组件异常增长。
加分回答 :启用 innodb_buffer_pool_dump_at_shutdown 和 innodb_buffer_pool_load_at_startup,使重启后快速预热 Buffer Pool,减少短期命中率骤降的影响。同时,使用 PMM 的 InnoDB Details 面板观察命中率趋势,结合磁盘 I/O 面板确认物理读突增。
7.9 sys.io_global_by_file_by_bytes 和 sys.schema_tables_with_full_table_scans 如何配合使用定位 I/O 瓶颈?
一句话回答 :io_global_by_file_by_bytes 找出 I/O 最高的数据文件,schema_tables_with_full_table_scans 显示哪些表在被全表扫描,如果两者指向同一张表,就找到了 I/O 瓶颈的元凶。
详细解释 : io_global_by_file_by_bytes 按文件(如 order_db/orders.ibd)的读写字节数排序,能直观看到哪个表的物理 I/O 最高。同时,schema_tables_with_full_table_scans(或查询 sys.statements_with_full_table_scans 配合 object_schema)列出正在全表扫描的表。当某表既出现在 I/O 热点文件中,又出现在全表扫描列表中,说明该表的全表扫描直接导致了大量磁盘读,应优先为其优化索引。
追问1 :如果没有全表扫描查询,但 I/O 仍然高,可能是什么? 答:可能是大量索引范围扫描或写入导致的脏页刷新。可查看 sys.io_global_by_wait_by_latency 了解 I/O 等待分布,以及 performance_schema.table_io_waits_summary_by_table 查看写 I/O 统计。
追问2 :如何区分随机 I/O 和顺序 I/O? 答:全表扫描通常产生顺序读,索引回表多为随机读。通过 performance_schema.file_summary_by_instance 的 AVG_TIMER_READ 和 COUNT_READ 可大致判断:如果读取次数多但平均延迟低,可能是顺序读;平均延迟高且次数相对少,可能是随机读。
追问3:这两个视图的数据时效性如何? 答:都是自数据库启动以来的累计值,如需实时增量,可定期快照并计算差值。
加分回答 :在 PMM 中,同时叠加 Disk I/O 面板和 Table I/O 面板,可以得到时间序列化验证。还可以使用 pt-ioprofile 工具,直接统计进程的系统 I/O 调用,精确定位热点文件和操作类型。
7.10 performance_schema.events_waits_current 能提供哪些诊断信息?如何解读等待事件?
一句话回答:它显示每个线程当前正在等待的事件类型(如 I/O、锁、同步对象等)和等待时间,帮助判断语句卡在哪个资源上。
详细解释 : events_waits_current 表每一行对应一个线程当前或最近等待的事件,包括 EVENT_NAME(如 wait/io/file/innodb/innodb_data_file)、TIMER_WAIT(等待时间)、OPERATION(如 read、write)等。通过查询该表,可以瞬间获取整个实例等待概况:
- 大量
wait/io/file表示 I/O 瓶颈。 wait/synch/mutex/innodb/trx_sys_mutex表示事务系统压力大。wait/lock/metadata/sql/mdl表示元数据锁争用。 将等待事件与具体的线程 ID、查询关联,就能把宏观等待归因到具体 SQL。
追问1 :如何把一个慢查询与其等待事件关联起来? 答:通过 performance_schema.threads 找到 THREAD_ID,然后关联 events_statements_current 获取当前执行的 SQL,再连接 events_waits_current 看到它的等待事件。
追问2 :如果大量线程在 wait/io/table/sql/handler 等待,是什么问题? 答:这通常表示存储引擎层面的 I/O 操作等待,可能是全表扫描或索引扫描带来的大量磁盘读,需要优化查询或增加索引。
追问3 :等待事件的时间单位是什么?如何转换为秒? 答:TIMER_WAIT 以皮秒(picosecond)为单位,除以 1e12 可得秒。sys schema 中的视图已自动转换为人类可读格式。
加分回答 :利用 sys.waits_global_by_latency 视图,汇总全局等待事件按总等待时间排序,快速识别整个实例的主要等待源头。结合 pt-pmp(Percona 的堆栈分析工具)还可从操作系统层面确认进程堆栈,进一步验证等待事件。
7.11 故障排查题:线上数据库 CPU 突然飙高,慢查询日志暴涨,pt-query-digest 显示一个之前很快的查询现在占据了 60% 的响应时间,请给出完整的排查思路。
一句话回答:按照"宏观确认→指纹聚合→执行计划对比→索引/统计信息修复→验证"的闭环排查。
详细解释:
- 宏观确认(PMM/Grafana):检查 QPS 是否等比例上升,排除流量突增。确认慢查询速率暴增,并通过 Query Analytics 锁定具体查询模板。
- 离线指纹分析(pt-query-digest):导出问题时段慢日志,生成报告,查看该查询的 V/M 和直方图,判断是整体变慢还是部分参数变慢。
- 诊断快照(sys schema) :使用
sys.statement_analysis查看该查询的rows_examined_avg、tmp_disk_tables、full_scan等指标。检查schema_unused_indexes确认索引是否仍被使用。 - 执行计划验证(EXPLAIN 与 EXPLAIN ANALYZE) :获取该查询的当前执行计划,并与历史正常计划对比(如有基线)。重点看
key、rows、Extra。若发现索引未被使用或访问类型从range变为ALL,则使用EXPLAIN ANALYZE实测行数和时间,定位扫描放大点。 - 根因分析 :
- 若统计信息过期(
actual rows远大于rows),执行ANALYZE TABLE;或发现直方图缺失。 - 若索引失效或设计不合理,依据第2篇原则添加或调整索引。
- 若涉及锁等待,查询
sys.innodb_lock_waits结合第4篇分析。 - 若发现该查询因数据量变化导致优化器选错连接顺序,参考第5篇调整
optimizer_switch。
- 若统计信息过期(
- 实施优化并验证 :添加索引或更新统计后,再次
EXPLAIN ANALYZE确认行数和时间改善。观察 PMM CPU、慢查询速率、QAN 延迟是否恢复正常。
追问1 :如果 EXPLAIN ANALYZE 显示优化器选择了错误的连接顺序怎么办? 答:可以通过 STRAIGHT_JOIN 或调整 SET optimizer_switch='block_nested_loop=off' 等来干预。如果是统计信息问题,增加采样页或创建直方图。参考第5篇。
追问2 :如果数据库没有开启 performance_schema,如何诊断? 答:只能依赖慢查询日志、SHOW ENGINE INNODB STATUS、SHOW PROCESSLIST 和 EXPLAIN,效率低下。建议尽快启用(注意开销)。
追问3 :发现是一个 JOIN 查询突然变慢,如何定位是哪张表扫描放大? 答:EXPLAIN ANALYZE 迭代器树会清晰显示各表的 actual rows。例如 Nested loop 内部表的 actual rows 乘以外表行数巨大,则内表是瓶颈。
加分回答 :在紧急情况下,可先用 KILL 终止堆积的慢查询,或通过中间件限流。事后使用 pt-query-digest 的 --output=slowlog 提取问题 SQL 到测试环境,使用不同参数重现并优化,形成优化案例。
7.12 场景分析题:系统频繁出现 Lock_time 超过 500ms 的慢查询,但 EXPLAIN 显示这些查询都正确使用了索引,如何结合 sys schema、performance_schema 和第4篇的锁知识进行分析和解决?
一句话回答 :索引正确使用不代表不会锁等待;高 Lock_time 通常由其他事务持有锁阻塞导致,需通过锁等待视图定位阻塞源头,再根据第4篇的加锁规则判断为何持有锁的范围或时间超出预期。
详细解释:
- 实时锁等待分析(sys.innodb_lock_waits) :当发现
Lock_time高的慢查询时,立即查询sys.innodb_lock_waits,查看这些查询是否正在被阻塞,记录blocking_trx_id和blocking_query。 - 阻塞事务深入检查 :对于阻塞事务,通过
performance_schema.data_locks查看其持有的锁类型(LOCK_MODE)和锁定的索引、记录范围。即使被阻塞的查询使用了索引,如果阻塞事务的锁范围(如 Gap Lock)覆盖了它要访问的记录,也会等待。第4篇详细阐述了非唯一索引范围查询会加 Next‑Key Lock,可能导致意外的间隙锁冲突。 - 检查阻塞事务是否长事务 :查询
performance_schema.events_transactions_current,看阻塞事务的TIMER_WAIT(持续时间)。若很长,可能是未提交的闲置事务,应尽快提交或回滚。 - 优化阻塞事务自身 :如果阻塞事务是正常的业务
UPDATE,但本身执行很慢(Rows_examined高),那么它会长时间持有锁,阻塞其他事务。此时应优化这个阻塞事务的查询计划,添加索引或缩小范围,减少锁持有时间。 - 调整加锁策略 :如果锁等待因间隙锁导致,评估是否可以改用
READ COMMITTED隔离级别以避免间隙锁(需评估业务一致性影响,详见第4篇)。或者将事务内的操作顺序调整一致,避免死锁。 - 持续监控 :使用
pt-deadlock-logger或 PMM 死锁监控,定期抓取锁等待信息,建立锁等待基线。
追问1 :如果阻塞事务是 SELECT ... FOR UPDATE 且索引完美,为什么会锁很多行? 答:该语句可能使用范围条件,在非唯一索引上会加 Next‑Key Lock,锁定多个间隙。即使只返回少量行,锁定的间隙也可能很大。这需要根据第4篇的加锁规则精确分析索引类型和条件。
追问2 :如何区分 Lock_time 是等待行锁还是元数据锁? 答:Lock_time 是总锁等待时间,包含所有锁。通过 sys.schema_table_lock_waits 可单独查看元数据锁等待;也可查询 performance_schema.metadata_locks 了解当前 MDL 持有情况。
追问3 :能否通过降低隔离级别解决? 答:可以尝试将隔离级别从 REPEATABLE READ 降至 READ COMMITTED,这会禁用间隙锁,从而减少锁冲突。但必须确保业务逻辑不依赖可重复读,比如报表查询可能不一致。第4篇有详细讨论隔离级别与加锁。
加分回答 :结合 pt-query-digest 的 Lock time 占比分析,找出锁等待最严重的查询。在业务低峰期,抓取 performance_schema 的 data_locks 和 data_lock_waits 快照,重建锁等待历史,用于事后分析。
慢查询诊断工具速查表
| 工具 | 作用范围 | 关键命令 / 视图 | 输出重点 | 关联知识篇章 |
|---|---|---|---|---|
| 慢查询日志 | 采集慢 SQL | SET GLOBAL slow_query_log=ON; |
Query_time, Rows_examined, Lock_time |
本文第1节 |
pt-query-digest |
查询指纹与聚合 | pt-query-digest slow.log |
Profile 排名、直方图、V/M、Review | 本文第2节,第5篇优化器 |
pt-index-usage |
索引使用分析 | pt-index-usage slow.log |
未使用索引列表 | 第2篇索引原理 |
| PMM Query Analytics | 实时查询监控 | 面板 MySQL Query Analytics |
延迟趋势、负载百分比 | 本文第3节 |
| PMM InnoDB Details | 引擎层监控 | InnoDB Details 面板 |
Buffer Pool 命中率、Row Lock Wait | 第4篇锁机制,第2篇 |
sys.statement_analysis |
高消耗 SQL | SELECT * FROM sys.statement_analysis ... |
总延迟、扫描行数、临时表、全表扫描 | 第2篇、第5篇 |
sys.schema_unused_indexes |
冗余索引 | SELECT * FROM sys.schema_unused_indexes |
未使用的索引名 | 第2篇 |
sys.innodb_lock_waits |
锁等待链 | SELECT * FROM sys.innodb_lock_waits |
等待与阻塞事务的 SQL | 第4篇 |
EXPLAIN ANALYZE |
实际执行计划 | EXPLAIN ANALYZE SELECT ... |
迭代器 actual time、actual rows |
第5篇优化器 |
performance_schema.events_statements_summary_by_digest |
查询聚合 | SQL 查询 | 精确时间、锁时间、行扫描 | 第5篇、本文第5节 |
performance_schema.events_waits_current |
等待事件 | 关联 threads |
当前等待事件名称与时间 | 全文诊断 |
pt-heartbeat |
复制延迟 | pt-heartbeat --monitor |
Seconds_Behind_Master |
第6篇主从复制 |
延伸阅读
- 《高性能MySQL》第4版,第8、9、10章(性能优化与诊断)
- Percona Toolkit 官方文档:docs.percona.com/percona-too...
- PMM 文档:docs.percona.com/percona-mon...
- MySQL 8.0 官方文档
sys schema章节:dev.mysql.com/doc/refman/...