EXPLAIN 与 EXPLAIN ANALYZE 的区别(8.0+)?后者真正执行 SQL 估算成本,前者不执行?
What is the difference between EXPLAIN and EXPLAIN ANALYZE (8.0+
考察要点
这道题面试官想看你是否懂 MySQL 8.0+ 中执行计划分析的两种核心工具,并能清晰区分它们的原理、成本和适用场景。重点在于理解**“估算”(Estimation)与“实际执行”(Actual Execution)**的本质差异,以及如何利用 EXPLAIN ANALYZE 提供的真实数据来精准定位性能瓶颈。这体现了你从“会用 EXPLAIN”到“精通性能诊断”的进阶能力。
核心概念
EXPLAIN 和 EXPLAIN ANALYZE 都是用于分析 SQL 查询性能的工具,但它们在原理和提供的信息上有本质区别。
1. EXPLAIN
EXPLAIN 是 MySQL 一直以来都有的执行计划查看工具。
- 核心原理:静态估算。它通过查询优化器(Optimizer)来模拟 SQL 的执行过程,但并不会真正执行 SQL 语句。优化器会依据表的统计信息(如 Cardinality、数据分布直方图等)来估算不同执行路径的成本(Cost),并选择一个它认为成本最低的计划。
- 输出内容:一个表格,展示了查询的访问类型、使用的索引、估算的扫描行数等。
type: 连接类型,从system>const>eq_ref>ref>range>index>ALL,性能依次降低。key: 实际使用的索引。rows: 优化器估算的为了找到所需行而需要读取的行数。这是估算值,可能与实际情况差异巨大。Extra: 额外信息,如Using index(覆盖索引),Using filesort(需要额外排序),Using temporary(使用了临时表)。
- 优点:
- 安全:不会对数据产生任何影响,即使是
UPDATE或DELETE语句也可以安全使用。 - 快速:几乎是瞬时返回结果,因为它只做静态分析。
- 安全:不会对数据产生任何影响,即使是
- 缺点:
- 不准确:所有关键指标(尤其是
rows)都是估算值。当统计信息陈旧、数据分布不均或查询逻辑复杂时,估算可能严重失准,导致你对性能瓶颈产生误判。
- 不准确:所有关键指标(尤其是
2. EXPLAIN ANALYZE (MySQL 8.0.18+)
EXPLAIN ANALYZE 是 MySQL 8.0 引入的强大诊断工具,它弥补了 EXPLAIN 纯估算的不足。
- 核心原理:实际执行并测量。它会真正地执行 SQL 语句,并在执行过程中通过“迭代器”(Iterator)模型来测量每个执行节点的真实耗时、返回的实际行数等指标。
- 输出内容:一个树状的结构,展示了详细的执行信息。
text1-> Nested loop join (cost=... rows=...) (actual time=0.123..0.456 rows=100 loops=1)2 -> Table scan on t1 (cost=... rows=...) (actual time=0.010..0.020 rows=10 loops=1)3 -> Index lookup on t2 using idx_t1_id (t1_id=t1.id) (cost=... rows=...) (actual time=0.030..0.040 rows=10 loops=10)
(actual time=t1..t2 rows=r loops=l)是其精髓:actual time:t1是获取第一行数据的时间(ms),t2是获取所有行数据的总时间(ms)。这个时间差可以帮你判断数据传输的瓶颈。rows: 该节点实际返回的行数。这是与EXPLAIN中估算rows最核心的区别。loops: 该节点被执行的次数。在嵌套循环连接(Nested Loop Join)中,内层表的loops次数等于外层表返回的行数。
- 优点:
- 准确:提供了查询执行的真实性能数据,让你能精确地知道时间花在了哪个环节,哪个节点的估算与实际差异最大。
- 信息丰富:通过
actual time和loops可以深入分析 Join、排序、聚合等操作的实际开销。
- 缺点:
- 有成本/风险:因为它会实际执行查询,对于慢查询,
EXPLAIN ANALYZE本身也会很慢。更重要的是,如果对UPDATE,DELETE,INSERT等 DML 语句使用,它会真实地修改数据,这是在生产环境使用的最大风险。
- 有成本/风险:因为它会实际执行查询,对于慢查询,
对比总结
| 特性 | EXPLAIN | EXPLAIN ANALYZE (MySQL 8.0.18+) |
|---|---|---|
| 核心原理 | 静态估算,不执行 SQL | 实际执行 SQL 并测量性能 |
| 查询执行 | 否 | 是 |
| 成本/风险 | 低,安全快速 | 高,耗时且对 DML 会修改数据 |
| 输出信息 | 估算的成本、行数、索引使用等 | 估算信息 + 真实的执行时间、行数、循环次数 |
| 准确性 | 可能不准,依赖统计信息 | 非常准确,反映真实执行情况 |
| 适用场景 | 日常快速检查、初步分析、对任何 SQL 都安全 | 深入诊断、当 EXPLAIN 估算严重失准时、仅推荐对 SELECT 或在事务中对 DML 使用 |
| 版本支持 | 所有版本 | MySQL 8.0.18+,MariaDB 10.1+ |
常见低分回答
- 混淆概念:“
EXPLAIN ANALYZE只是EXPLAIN的一个增强版,估算的更准一些,但也不执行 SQL。”- 错误点:这是最根本的错误。
ANALYZE的核心就是实际执行,它的准确性来源于真实测量而非更高级的估算。
- 错误点:这是最根本的错误。
- 滥用在 DML 上:“我有一个
DELETE语句很慢,我直接在生产库上用EXPLAIN ANALYZE看看是为什么。”- 错误点:极度危险!这会导致数据被真实删除。没有提及风险或在事务中回滚的保护措施,是面试中的重大扣分项。
- 无法解读输出:“
EXPLAIN ANALYZE的结果里,actual time和loops我看不太懂,我主要还是看它估算的rows。”- 错误点:本末倒置。
EXPLAIN ANALYZE的价值就在于actual time、actual rows和loops这些真实数据,用它来验证和推翻EXPLAIN的估算。如果只看估算值,就失去了使用它的意义。
- 错误点:本末倒置。
- 忽略版本:“我在 MySQL 5.7 的项目里也经常用
EXPLAIN ANALYZE。”- 错误点:事实性错误,暴露了候选人可能缺乏对 MySQL 版本演进的了解或实际经验不足。
工程实践与优化
在生产环境中,我们通常遵循一个从低成本到高成本的诊断流程:
-
初步诊断(使用
EXPLAIN):- 对于任何新上线的或发现有性能问题的 SQL,首先使用
EXPLAIN。 - 重点关注
type是否为ALL或index,key是否为NULL,Extra中是否出现Using filesort或Using temporary。 - 这能解决 80% 的常见索引问题。
- 对于任何新上线的或发现有性能问题的 SQL,首先使用
-
深入分析(使用
EXPLAIN ANALYZE):- 当
EXPLAIN的结果看似“完美”(例如,用上了索引,rows估算值很小),但查询依旧很慢时,就轮到EXPLAIN ANALYZE出场了。 - 核心任务:找出优化器估算错误的地方。对比
EXPLAIN的rows和EXPLAIN ANALYZE的actual rows,差异最大的节点往往就是性能瓶颈所在。 - 场景示例:一个 Join 查询,
EXPLAIN估算外层表返回 10 行,内层表循环 10 次。但EXPLAIN ANALYZE显示外层表实际返回了 100 万行,导致内层表被循环了 100 万次。此时,优化的方向就非常明确了:想办法优化外层表的过滤条件,减少其返回的行数。
- 当
-
安全执行
EXPLAIN ANALYZE:- 对于
SELECT查询:可以直接在从库或测试环境执行。如果在主库执行,要评估其本身可能带来的负载。 - 对于
UPDATE/DELETE/INSERT查询:严禁直接在生产主库执行。正确的做法是:通过事务包裹并最终回滚,可以在获取真实执行计划的同时,保证数据不会被修改。sql1START TRANSACTION;2EXPLAIN ANALYZE UPDATE my_table SET status = 1 WHERE id = 123;3ROLLBACK;
- 对于
-
结合工具:
- 在定位到具体慢查询之前,通常会使用
pt-query-digest分析慢查询日志(slow log),或者通过Performance Schema来找到最高频、最耗时的 SQL,然后再针对性地使用EXPLAIN和EXPLAIN ANALYZE。
- 在定位到具体慢查询之前,通常会使用
常见追问与延伸题目
-
追问:为什么优化器会估算错行数(
rows)?导致EXPLAIN的结果和EXPLAIN ANALYZE的实际结果差异巨大?- 回答要点 1: 统计信息陈旧或不准确。
InnoDB默认是基于采样的估算,当表数据发生大量增删改后,如果没及时执行ANALYZE TABLE,统计信息就会过时,导致估算严重偏离。 - 回答要点 2: 复杂谓词条件。
WHERE子句中包含多个OR条件、范围查询、函数调用、或者列与列之间的比较(如WHERE col1 > col2),优化器很难精确估算其选择性(Selectivity),通常会依赖一些固定的启发式规则,这往往不准。 - 回答要点 3: 数据分布不均(数据倾斜)。优化器通常假设数据是均匀分布的。如果某个值的出现频率远高于其他值,而查询恰好命中了这个高频值,那么估算的行数(基于平均分布)会远小于实际行数。
- 回答要点 4: 关联查询的复杂性。在多表
JOIN中,错误的估算会层层放大。第一层JOIN的行数估算错误,会作为第二层JOIN的输入,导致错误被急剧放大。
- 回答要点 1: 统计信息陈旧或不准确。
-
追问:
EXPLAIN ANALYZE的输出中,actual time=0.123..0.456这两个时间分别代表什么?它们对于性能分析有什么具体意义?- 回答要点 1:
t1(0.123ms) 是 "Time to first row"。代表当前这个执行节点(Iterator)从开始执行到返回第一行数据所花费的时间。这个时间主要包含了节点的初始化、定位到第一条满足条件的记录等开销。 - 回答要点 2:
t2(0.456ms) 是 "Time to last row"。代表当前节点从开始执行到返回所有行数据所花费的总时间。 - 回答要点 3: 分析意义。
- 如果
t1很大,但t2-t1很小,说明找到第一行数据的成本很高,但一旦找到,后续数据返回很快。这常见于需要排序(filesort)或聚合的操作,因为它们需要处理完所有输入数据才能产出第一行结果。 - 如果
t1很小,但t2-t1很大,说明找到第一行很快,但后续需要处理或传输大量数据。这常见于没有合适索引的全表扫描,或者 Join 操作返回了大量结果集。这提示我们应该关注如何减少返回的总行数。
- 如果
- 回答要点 1:
-
追问:如果换成 PostgreSQL,
EXPLAIN ANALYZE有什么不同?- 回答要点 1: PostgreSQL 是先行者。PostgreSQL 很早就提供了
EXPLAIN ANALYZE功能,MySQL 在这方面是借鉴了其设计思想。 - 回答要点 2: 输出信息更丰富。PostgreSQL 的
EXPLAIN (ANALYZE, BUFFERS)命令可以提供更详尽的信息。除了执行时间,它还会显示:Buffers: 共享内存块(shared blocks)的命中(hit)、读取(read)、写入(dirtied/written)等信息。这能让你清晰地判断查询是 I/O 密集型还是 CPU 密集型。I/O Timings: 如果开启了track_io_timing,它还能显示精确的块设备读写时间。WAL: 与预写日志相关的记录数和字节数。
- 回答要点 3: 结论。虽然名字相似,但 PostgreSQL 的
EXPLAIN ANALYZE提供了更底层的存储和 I/O 性能视图,对于极限性能调优更加强大。这体现了不同数据库在可观察性(Observability)设计上的差异。
- 回答要点 1: PostgreSQL 是先行者。PostgreSQL 很早就提供了