慢查询与性能诊断:PMM、pt-query-digest 与 sys schema

概述

摘要 :前文《分库分表与 ShardingSphere‑JDBC》展示了分布式数据分片方案,但无论架构如何演进,单条慢查询仍可能拖垮整个系统。如何在海量 SQL 中精准定位慢查询?如何从监控告警反推根因?如何避免"CPU 飙高就加索引"的盲目优化?本文从慢查询日志的底层格式出发,构建以 pt‑query‑digest + PMM + sys schema + EXPLAIN ANALYZE + performance_schema 为核心的五维诊断体系,并以前文的核心原理(索引、锁、优化器、复制)为理论支撑,形成一套可复用、可追溯的排查决策树。

文章组织架构

flowchart TB 1[1. 慢查询日志机制与配置策略] --> 2[2. pt-query-digest查询指纹与响应时间分析] 2 --> 3[3. PMM监控体系与指标关联分析] 3 --> 4[4. sys schema性能诊断视图集] 4 --> 5[5. EXPLAIN ANALYZE与performance_schema精细诊断] 5 --> 6[6. 系统化排查决策树与场景推演] 6 --> 7[7. 面试高频专题] 1 --> 5 2 --> 4 3 --> 6

总览说明:全文 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 PROCESSLISTperformance_schema.threads 中关联。
  • Errno :语句执行时的错误号,0 表示正常。Killed 标记表示语句是否被主动杀死。
  • Bytes_receivedBytes_sent:客户端与服务器的传输字节数,可辅助判断网络开销。
  • Schema:执行时所在的默认数据库,用于按业务库过滤。
  • SET timestamp :Unix 时间戳,精确到秒。结合 # Time 可做时间序列分析。

当启用 log_slow_extra = ON(MySQL 8.0.14+)时,还会记录 Rows_affectedBytes_sentBytes_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_indexesmin_examined_row_limit 避免日志风暴
log_throttle_queries_not_using_indexes 10 ~ 60 条/分钟 限制每分钟记录未使用索引语句的数量,生产必备
min_examined_row_limit 1000 ~ 10000 过滤那些未使用索引但扫描行数极少的"小查询",减少噪音
log_slow_admin_statements ON 记录 OPTIMIZE TABLEALTER 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_idErrnoRows_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 五维诊断体系全景图

在进入工具链之前,先展示五维诊断体系的整体流转关系,揭示它们如何互补。

flowchart TD A[慢查询日志] --> B[pt-query-digest] A --> C[PMM Agent 采集] B --> D{Top-N SQL 模板} B --> E[pt-index-usage 索引使用分析] C --> F[PMM Query Analytics] C --> G[PMM InnoDB / Replication] D --> H[sys schema 视图集] F --> H G --> H H --> I[EXPLAIN ANALYZE 实际执行] H --> J[performance_schema 等待事件] I --> K[根因确认与优化] J --> K

图表概述 :该图以慢查询日志为中心数据源,向上游对接 pt-query-digest 离线分析和 PMM 实时采集,向下游通过 sys schema 进行多维诊断,最终用 EXPLAIN ANALYZEperformance_schema 做精细验证,形成闭环。

核心元素解读

  • pt-query-digest 将日志指纹化,给出按总响应时间排序的 Profile,是定位头部问题 SQL 的利器。
  • PMM 的 Query Analytics 提供时间序列趋势,可回答"何时开始变慢"和"是偶发还是持续"。
  • sys schema 的视图集则提供快捷的诊断快照,直接列出全表扫描、未使用索引、锁等待等,无需记忆底层表。
  • EXPLAIN ANALYZEperformance_schema 负责精细验证,定位到优化器偏差或具体等待事件。

诊断应用场景 :当 CPU 飙高时,可先从 PMM 查看 QPS 与慢查询趋势,然后用 pt-query-digest 分析对应时段日志,锁定 Top SQL,再通过 sys schemaEXPLAIN ANALYZE 找到根因(如全表扫描或统计信息过期)。

与前文知识关联:整个流程中,涉及索引缺失时调用第2篇,锁等待时调用第4篇,优化器统计信息错误时调用第5篇,主从延迟时调用第6篇。图中各诊断步骤会显式指向对应篇章。

2. pt-query-digest:查询指纹与响应时间分析

2.1 查询指纹算法深度解析

pt-query-digest 的查询指纹(Query Fingerprint)是将 SQL 文本归一化的核心算法,其设计目标是将功能相同的查询归为一类,即使具体字面量不同。算法步骤:

  1. 词法分析:基于 MySQL 的 SQL 解析器(来源于 DBD::mysql 或 MySQL 源码),将 SQL 字符串分解为 token 流,识别关键字、标识符、操作符、字面量等。
  2. 字面量替换 :将以下类型的字面量统一替换为 ?
    • 整数、浮点数(如 123, 3.14
    • 字符串常量(如 'active'
    • 日期/时间常量(如 '2026-05-01'
    • 二进制字面量(如 x'1A'
    • NULL 字面量(有些情况保留关键字 NULL,但具体数值值被替换)
    • 列表中的多个值如 IN (1,2,3) 会被替换为 IN (?)
  3. 空白标准化:将多余的空格、换行压缩为单个空格。
  4. 大小写统一:将 SQL 关键字和标识符转换为小写(或统一形式)。
  5. 计算摘要 :对标准化后的字符串计算 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_idstatus 的值不同。

该指纹机制与 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
  • QPSconcurrency:该查询在统计时间窗口内的每秒调用次数和平均并发度(每秒活跃执行次数),用于评估是否突发流量导致。
  • Query_time distribution:执行时间分布的直方图,采用对数尺度。上图显示绝大多数执行集中在 100ms 左右,但有少量执行达到 1s 甚至 10s+,这提示可能某些参数导致优化器选错索引或大量扫描。
  • Tables :工具自动执行 SHOW TABLE STATUSSHOW 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 支持多种数据源,适用于不同场景:

  • 慢查询日志文件 :最常用,直接指定文件或管道。

    bash 复制代码
    pt-query-digest /var/log/mysql/slow.log
  • SHOW PROCESSLIST 实时采样 :适合无法开启慢日志或需要捕获当前瞬间问题。

    bash 复制代码
    pt-query-digest --processlist h=localhost --iterations 10 --run-time 60s
  • tcpdump 网络包 :在无法接触数据库或需要分析网络层延迟时使用。

    bash 复制代码
    tcpdump -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 聚合流程图

flowchart TD A[原始数据源: 慢查询日志 / processlist / tcpdump] --> B[词法解析器: 识别 SQL 结构] B --> C[字面量替换: 数值、字符串 → ?] C --> D[空白标准化与大小写统一] D --> E[计算 MD5 指纹] E --> F[按指纹分组聚合] F --> G[计算统计信息: Calls, 总时间, 分布] G --> H[按响应时间排序生成 Profile] H --> I[输出 Details 直方图与 EXPLAIN] H --> J[输出 Review 建议]

图表概述 :该图描绘了 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 STATUSSHOW VARIABLESperformance_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 核心监控指标关联分析图

flowchart TB A[QPS/TPS 平稳] --> B[慢查询速率上升] B --> C[QAN Top SQL 延迟增加] C --> D{全表扫描 / 索引失效} D --> E[Buffer Pool 命中率下降] E --> F[磁盘读 I/O 上升] F --> G[锁持有时间变长] G --> H[InnoDB Row Lock Wait 激增] H --> I[连接等待 / 超时] H --> J[主从延迟扩大] J --> K[Replication Lag 飙升] A --> L[连接数暴增] L --> H

图表概述:该因果链从宏观 QPS 到慢查询、全表扫描、I/O、锁等待,最终扩散到复制延迟,展示了典型性能雪崩的传递路径。

核心元素解读

  • 慢查询增加往往是全表扫描、无效索引或统计信息过期所致。
  • 全表扫描将大量数据页读入 Buffer Pool,导致热点页被挤出,命中率下降,产生大量磁盘读。
  • 磁盘 I/O 加重会拖慢所有语句的执行,锁持有时间变长,进而引发锁等待堆积。
  • 锁等待与 I/O 抖动会影响主库的 Binlog 写入吞吐,结合大事务可能加剧从库延迟。

诊断应用场景:当 PMM 仪表盘同时出现命中率下降、锁等待增加和复制延迟时,首先检查 QAN 中是否出现新的高负载查询,然后沿此链路逐一验证。

与前文知识关联:锁等待分析深入依赖第4篇;优化器选择错误导致全表扫描需参考第5篇;主从延迟原因见第6篇。

4. sys schema 性能诊断视图集

sys schemaperformance_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_selectedrows_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 BYGROUP BY 无法使用索引造成。
  • statements_with_full_table_scans:执行全表扫描的查询,直接指向索引缺失或失效。
  • statements_with_sorting:使用文件排序(Using filesort)的查询,如果排序行数大,会消耗大量 CPU 和内存。

诊断价值statement_analysis 可视为内置的 pt-query-digest 概要,用于实时获取高消耗 SQL。全表扫描和临时表查询需重点关注,前者往往因索引失效,后者可能因 ORDER BYGROUP 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 诊断视图分类图

flowchart TD A[sys schema 诊断视图] --> B[索引分析] A --> C[查询分析] A --> D[I/O 与磁盘] A --> E[内存分析] A --> F[锁与等待] A --> G[复制分析] B --> B1[schema_unused_indexes] B --> B2[schema_redundant_indexes] B --> B3[schema_index_statistics] C --> C1[statement_analysis] C --> C2[statements_with_temp_tables] C --> C3[statements_with_full_table_scans] C --> C4[statements_with_sorting] D --> D1[io_global_by_file_by_bytes] D --> D2[io_global_by_wait_by_latency] E --> E1[memory_global_by_current_bytes] E --> E2[memory_by_thread_by_current_bytes] F --> F1[innodb_lock_waits] F --> F2[schema_table_lock_waits] G --> G1[replication_status] G --> G2[replication_applier_status_by_worker]

图表概述 :此图将 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 timeactual rowsloops

输出解读

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)
  • costrows:优化器估算的代价和行数。
  • actual time :格式 start..end(毫秒),start 是返回第一行的时间(启动时间),end 是返回所有行的总时间。两者差值大说明需要处理大量行后才能返回(如排序、分组)。
  • actual rows :该迭代器实际返回的行数。如果某节点 actual rows 远大于其父节点或估算 rows,则是性能瓶颈------大量行被读取后丢弃。
  • loops:该迭代器被循环执行的次数(Nested Loop Join 中内部表会多次扫描)。

对比诊断方法

  1. 定位 actual rows 最大的叶子节点(最内层),通常是索引扫描或全表扫描。
  2. 检查该节点的 actual rows 与估算 rows 的差异。若差异巨大,说明统计信息失真,需 ANALYZE TABLE 或调整 innodb_stats_persistent_sample_pages(第5篇)。
  3. 如果估算正确但实际行数仍大,说明查询条件本身选择性差,需要改索引。
  4. 观察 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_lengthperformance_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_nameSUM_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_bytestable_io_waits_summary_by_table 可共同确认热点表,然后通过 statement_analysis 找到访问该表的查询,最后用 EXPLAIN ANALYZE 验证。

6. 系统化排查决策树与场景推演

6.1 四场景排查决策树总图

以下决策树整合了前文所有工具与前文原理(索引、锁、优化器、复制),给出四大典型故障的标准化诊断路径。

flowchart TB START["收到告警 / 观察到异常"] --> A{"异常类型判断"} A -- "CPU 飙高 / 慢查询暴涨" --> B1["PMM QPS 与 CPU 趋势确认非流量增加"] B1 --> B2["pt-query-digest 分析对应时段慢日志"] B2 --> B3{"Profile 中 Top SQL 的 V/M 与直方图特征?"} B3 -- "V/M 小,整体慢" --> B4["EXPLAIN ANALYZE 查看实际执行计划"] B4 --> B5{"执行计划显示全表扫描或索引失效?"} B5 -- 是 --> B6["检查 sys.schema_unused_indexes 和索引选择性"] B6 --> B7["根据第2篇添加复合索引或覆盖索引"] B5 -- "否,但 actual rows >> rows" --> B8["执行 ANALYZE TABLE 更新统计信息 / 检查直方图,第5篇"] B3 -- "V/M 大,少量慢" --> B9["获取具体慢查询参数,EXPLAIN ANALYZE 模拟"] B9 --> B10["可能因数据分布倾斜导致优化器选错计划,第5篇"] B10 --> B7 B7 --> VALIDATE["验证: QPS/CPU 恢复,慢查询速率下降"] A -- "锁等待激增" --> C1["PMM InnoDB Row Lock Wait 趋势确认"] C1 --> C2["查询 sys.innodb_lock_waits 当前阻塞链"] C2 --> C3["关联慢查询日志中 Lock_time 高的 SQL"] C3 --> C4{"阻塞事务特征?"} C4 -- "长事务未提交" --> C5["查看 performance_schema.events_transactions_current 持续时间"] C5 --> C6["优化事务边界,及时提交,第4篇"] C4 -- "索引缺失导致锁范围扩大" --> C7["分析 WHERE 条件,检查索引选择性"] C7 --> C8["添加合适索引缩小锁粒度,第2篇/第4篇"] C8 --> VALIDATE A -- "磁盘 I/O 激增 / Buffer Pool 命中率下降" --> D1["sys.io_global_by_file_by_bytes 定位热点文件"] D1 --> D2["table_io_waits_summary_by_table 确定热点表"] D2 --> D3["statement_analysis 查找针对该表的全表扫描查询"] D3 --> D4{"是否索引缺失?"} D4 -- 是 --> D5["创建覆盖索引或重建索引,第2篇"] D4 -- 否 --> D6["评估数据归档、分区或更改查询方式"] D5 --> VALIDATE A -- "主从延迟恶化" --> E1["PMM Replication Lag 趋势确认"] E1 --> E2["performance_schema.replication_applier_status_by_worker 查看 Worker 延迟"] E2 --> E3["pt-query-digest 分析主库 Binlog (或慢日志) 寻找大事务"] E3 --> E4{"存在大事务?"} E4 -- 是 --> E5["拆分事务或批处理,关联第6篇"] E4 -- 否 --> E6["检查从库并行复制参数 slave_parallel_type,第6篇"] E5 --> VALIDATE E6 --> VALIDATE classDef decision fill:#fff4e6,stroke:#ff9800,stroke-width:2px,color:#333 classDef process fill:#f4f4f4,stroke:#333,stroke-width:1px classDef terminal fill:#e6f7e6,stroke:#4caf50,stroke-width:2px,color:#1e4620 class START,VALIDATE terminal class A,B3,B5,C4,D4,E4 decision class B1,B2,B4,B6,B7,B8,B9,B10,C1,C2,C3,C5,C6,C7,C8,D1,D2,D3,D5,D6,E1,E2,E3,E5,E6 process

图表概述:决策树将四大场景的排查路径结构化,每个分支都明确指定了使用的工具和诊断检查点,并显式引用了前文相关章节,形成可操作的"故障手册"。

核心元素解读

  • 所有路径都遵循"宏观确认 → 指纹/统计聚焦 → 执行计划/锁分析验证 → 原理指导修复 → 效果验证"的闭环。
  • 在 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 = 1tmp_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 是返回给客户端的行数。当两者相差巨大时,说明引擎做了大量无效的过滤工作。可能原因:

  1. 没有使用索引:导致全表扫描。
  2. 索引选择性低 :即使使用索引,但因为索引列过滤能力弱(如 status 只有三个值),仍会扫描大量行。
  3. 需要回表过滤 :索引覆盖不完整,需要回主表读取行再用 WHERE 筛选,导致扫描放大。
  4. 统计信息过期:优化器低估了行数,选择了错误的索引(关联第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_EXAMINEDSUM_ROWS_SENT,计算比率并定时快照,建立基线。sys.statement_analysis 也直接提供 rows_examined_avgrows_sent_avg

追问3:索引覆盖一定能解决扫描放大问题吗? 答:索引覆盖可避免回表,但如果索引本身仍需扫描大量行(因选择性差),则扫描放大依然存在,只是减少了回表 I/O。仍需优化索引列顺序或改写查询。

加分回答 :使用 EXPLAIN ANALYZEactual 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 模拟不同参数。

追问2pt-query-digestperformance_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_indexessys.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_selectedrows_insertedrows_updatedrows_deleted 等,可计算读写比。若某个索引读操作极少而写操作频繁,即使偶有使用,也可考虑删除以降低写入开销。

索引删除决策流程

  1. schema_unused_indexes 获取初步候选列表。
  2. 将候选索引通过 schema_index_statistics 确认读写统计,排除确实有低频读的索引。
  3. 使用 pt-index-usage 分析历史慢日志进一步验证。
  4. 在备库或非高峰时段先执行 ALTER TABLE ... ALTER INDEX idx_name INVISIBLE(MySQL 8.0),观察业务是否受影响。
  5. 确认无影响后执行 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 ANALYZEEXPLAIN 的根本区别?actual timeactual rows 对诊断有什么帮助?

一句话回答EXPLAIN 显示优化器的估算计划,EXPLAIN ANALYZE 实际执行并输出每个迭代器的真实耗时和行数。

详细解释EXPLAIN 完全基于统计信息和代价模型生成计划,不实际执行;EXPLAIN ANALYZE 会执行语句(对 InnoDB 自动回滚),测量每个迭代器的实际启动时间、总时间和返回行数。两者对比可以揭示:

  • 统计信息是否准确(估算 rows vs actual 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篇。

追问2EXPLAIN ANALYZE 会修改数据吗?生产环境能用吗? 答:对于 InnoDB,语句执行后会自动回滚,不会持久化修改。但对于 DML 会影响自增 ID 等(即使回滚也不会回收),应在低峰期或测试环境执行,或使用 BEGIN; EXPLAIN ANALYZE ...; ROLLBACK; 显式控制。

追问3 :如何解读 Nested loop inner join 中的 actual rowsloops? 答:loops 表示内部表被扫描的次数(通常等于外表行数),actual rows 是每次循环返回的行数。若 actual rows 很大且 loops 也大,则产生笛卡尔积效应,需优化连接条件或添加索引。

加分回答 :结合 EXPLAIN FORMAT=JSONEXPLAIN ANALYZE,可以得到代价详情和实际测量值,用于分析优化器的成本计算是否准确,进而调整 server_costengine_cost 表中的常数。

7.6 sys.statement_analysis 的数据来源是什么?与 performance_schema.events_statements_summary_by_digest 是什么关系?

一句话回答 :数据直接来源于 performance_schema.events_statements_summary_by_digestsys.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_avgrows_examined_avgtmp_disk_tablesfull_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 挤满表时,最旧的条目会被移除以腾出空间。可适当增大该参数。

追问2sys.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_latencyrows_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_idwaiting_queryblocking_trx_idblocking_querylocked_tablelocked_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_scansio_global_by_file_by_bytesschema_index_statistics 等。

详细解释: Buffer Pool 命中率下降意味着逻辑读减少,物理读增加。常见诱因:

  1. 全表扫描:将整个表的数据页读入 Buffer Pool,挤走热数据。
  2. 大范围索引扫描:虽然用了索引,但范围很大,同样加载大量页。
  3. 备份或导出 :如 mysqldump 不加 --quick 可能导致大查询。
  4. 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)。

追问3sys.memory_global_by_current_bytes 在这里有用吗? 答:可以查看 InnoDB buffer pool 实际分配的内存(memory/innodb/buf_buf_pool),确认是否与配置一致,以及是否有其他内存组件异常增长。

加分回答 :启用 innodb_buffer_pool_dump_at_shutdowninnodb_buffer_pool_load_at_startup,使重启后快速预热 Buffer Pool,减少短期命中率骤降的影响。同时,使用 PMM 的 InnoDB Details 面板观察命中率趋势,结合磁盘 I/O 面板确认物理读突增。

7.9 sys.io_global_by_file_by_bytessys.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_instanceAVG_TIMER_READCOUNT_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(如 readwrite)等。通过查询该表,可以瞬间获取整个实例等待概况:

  • 大量 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% 的响应时间,请给出完整的排查思路。

一句话回答:按照"宏观确认→指纹聚合→执行计划对比→索引/统计信息修复→验证"的闭环排查。

详细解释

  1. 宏观确认(PMM/Grafana):检查 QPS 是否等比例上升,排除流量突增。确认慢查询速率暴增,并通过 Query Analytics 锁定具体查询模板。
  2. 离线指纹分析(pt-query-digest):导出问题时段慢日志,生成报告,查看该查询的 V/M 和直方图,判断是整体变慢还是部分参数变慢。
  3. 诊断快照(sys schema) :使用 sys.statement_analysis 查看该查询的 rows_examined_avgtmp_disk_tablesfull_scan 等指标。检查 schema_unused_indexes 确认索引是否仍被使用。
  4. 执行计划验证(EXPLAIN 与 EXPLAIN ANALYZE) :获取该查询的当前执行计划,并与历史正常计划对比(如有基线)。重点看 keyrowsExtra。若发现索引未被使用或访问类型从 range 变为 ALL,则使用 EXPLAIN ANALYZE 实测行数和时间,定位扫描放大点。
  5. 根因分析
    • 若统计信息过期(actual rows 远大于 rows),执行 ANALYZE TABLE;或发现直方图缺失。
    • 若索引失效或设计不合理,依据第2篇原则添加或调整索引。
    • 若涉及锁等待,查询 sys.innodb_lock_waits 结合第4篇分析。
    • 若发现该查询因数据量变化导致优化器选错连接顺序,参考第5篇调整 optimizer_switch
  6. 实施优化并验证 :添加索引或更新统计后,再次 EXPLAIN ANALYZE 确认行数和时间改善。观察 PMM CPU、慢查询速率、QAN 延迟是否恢复正常。

追问1 :如果 EXPLAIN ANALYZE 显示优化器选择了错误的连接顺序怎么办? 答:可以通过 STRAIGHT_JOIN 或调整 SET optimizer_switch='block_nested_loop=off' 等来干预。如果是统计信息问题,增加采样页或创建直方图。参考第5篇。

追问2 :如果数据库没有开启 performance_schema,如何诊断? 答:只能依赖慢查询日志、SHOW ENGINE INNODB STATUSSHOW PROCESSLISTEXPLAIN,效率低下。建议尽快启用(注意开销)。

追问3 :发现是一个 JOIN 查询突然变慢,如何定位是哪张表扫描放大? 答:EXPLAIN ANALYZE 迭代器树会清晰显示各表的 actual rows。例如 Nested loop 内部表的 actual rows 乘以外表行数巨大,则内表是瓶颈。

加分回答 :在紧急情况下,可先用 KILL 终止堆积的慢查询,或通过中间件限流。事后使用 pt-query-digest--output=slowlog 提取问题 SQL 到测试环境,使用不同参数重现并优化,形成优化案例。

7.12 场景分析题:系统频繁出现 Lock_time 超过 500ms 的慢查询,但 EXPLAIN 显示这些查询都正确使用了索引,如何结合 sys schemaperformance_schema 和第4篇的锁知识进行分析和解决?

一句话回答 :索引正确使用不代表不会锁等待;高 Lock_time 通常由其他事务持有锁阻塞导致,需通过锁等待视图定位阻塞源头,再根据第4篇的加锁规则判断为何持有锁的范围或时间超出预期。

详细解释

  1. 实时锁等待分析(sys.innodb_lock_waits) :当发现 Lock_time 高的慢查询时,立即查询 sys.innodb_lock_waits,查看这些查询是否正在被阻塞,记录 blocking_trx_idblocking_query
  2. 阻塞事务深入检查 :对于阻塞事务,通过 performance_schema.data_locks 查看其持有的锁类型(LOCK_MODE)和锁定的索引、记录范围。即使被阻塞的查询使用了索引,如果阻塞事务的锁范围(如 Gap Lock)覆盖了它要访问的记录,也会等待。第4篇详细阐述了非唯一索引范围查询会加 Next‑Key Lock,可能导致意外的间隙锁冲突。
  3. 检查阻塞事务是否长事务 :查询 performance_schema.events_transactions_current,看阻塞事务的 TIMER_WAIT(持续时间)。若很长,可能是未提交的闲置事务,应尽快提交或回滚。
  4. 优化阻塞事务自身 :如果阻塞事务是正常的业务 UPDATE,但本身执行很慢(Rows_examined 高),那么它会长时间持有锁,阻塞其他事务。此时应优化这个阻塞事务的查询计划,添加索引或缩小范围,减少锁持有时间。
  5. 调整加锁策略 :如果锁等待因间隙锁导致,评估是否可以改用 READ COMMITTED 隔离级别以避免间隙锁(需评估业务一致性影响,详见第4篇)。或者将事务内的操作顺序调整一致,避免死锁。
  6. 持续监控 :使用 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-digestLock time 占比分析,找出锁等待最严重的查询。在业务低峰期,抓取 performance_schemadata_locksdata_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 timeactual 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篇主从复制

延伸阅读


相关推荐
敖正炀44 分钟前
主从复制与 GTID:半同步、并行复制
mysql
敖正炀1 小时前
InnoDB 引擎深度:B+Tree、页与行格式
mysql
敖正炀1 小时前
事务与 MVCC:Undo Log、ReadView 与隔离级别
mysql
老码观察1 小时前
K8s集群断电后MySQL恢复实录:从InnoDB崩溃到数据完整迁移
mysql·adb·kubernetes
敖正炀1 小时前
MySQL 架构全景与特性总览
mysql
tkevinjd1 小时前
MySQL1:分层架构
数据库·mysql·缓存
承渊政道2 小时前
从ROWNUM到LIMIT:KES、Oracle与PostgreSQL的执行顺序差异解析
数据库·数据仓库·sql·mysql·安全·postgresql·oracle
花生壳儿2 小时前
Docker容器安装MySQL数据库
数据库·mysql·docker
无小道2 小时前
Mysql——吃透事务以及隔离级别
mysql·面试·事务·隔离级别