在 MySQL 5.7 中,当我们怀疑某条 SQL 查询性能不佳时,最基础也最有效的手段就是在语句前加上 Explain 关键字。它能让数据库优化器把原本准备怎么执行这条语句的方案打印出来,而不是真正去跑数据。通过阅读这份“执行计划”,我们可以判断索引是否被正确使用、表之间的关联顺序是否合理,以及大概需要扫描多少行记录。

一、Explain 的基本用法
使用方式非常简单,只需要在 SELECT 语句前面直接加上 Explain 即可,MySQL 并不会真正执行查询,只会返回执行计划。例如我们有一张用户订单表 orders 和用户信息表 users,想查看联表查询的计划,可以写成如下形式。
EXPLAIN SELECT u.name, o.amount FROM users u JOIN orders o ON u.id = o.user_id WHERE o.status = 1 AND u.age > 20;
执行后通常会得到一张包含 id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra 等列的表格。每一行代表一个被访问的表,以及该表在被优化器安排下的访问方式。在 MySQL 5.7 里,如果使用了派生表或者子查询,还会通过 id 的大小体现出嵌套层级关系。
很多人初看 Explain 容易被一堆缩写劝退,其实只要抓住几个核心字段就能解决大部分问题。比如 type 显示了访问类型,rows 显示了预估扫描行数,key 显示了实际用到的索引。这三个值往往比复杂的理论更能直观反映 SQL 的健康程度。
二、关键字段逐一拆解
1. id 与 select_type
id 列表示查询中每个 SELECT 子句或者表的执行顺序,数值越大越先执行;如果相同,则按从上到下的顺序。对于单表查询,通常只有一行且 id 为 1。如果是子查询,MySQL 5.7 可能会把子查询物化成临时表,这时候就能从 id 差异看出主次关系。
select_type 则说明该行的查询类型,常见值有 SIMPLE(普通查询)、PRIMARY(外层查询)、SUBQUERY(子查询)、DERIVED(派生表)等。理解这些类型有助于我们分析复杂 SQL 的结构,尤其是当 Explain 出现多行结果时,能快速对应到原语句的哪一部分。
2. type 访问类型
type 是判断性能强弱最核心的一栏。从优到劣常见顺序为:system、const、eq_ref、ref、range、index、ALL。其中 const 和 eq_ref 多见于主键或唯一索引的精确匹配;ref 是普通索引等值查询;range 是索引范围扫描,比如用到了 BETWEEN 或 IN;而 ALL 代表全表扫描,在大数据表上通常是需要优化的信号。
举个例子,如果 orders 表的 user_id 上有普通索引,但查询条件只过滤了 status 而没有用到 user_id,就可能出现 type 为 ALL 的情况。这时候即便返回行数不多,数据库也要先扫全表再过滤,磁盘 IO 压力很大。我们可以通过调整 WHERE 顺序或建立联合索引来改善。
3. key 与 rows
possible_keys 表示优化器认为可能用得上的索引,而 key 是它最终选中的那个。有时候 possible_keys 不为空但 key 为 NULL,说明优化器评估后觉得全表扫描比走索引更划算,常见于命中数据比例过高的场景。rows 则是预估需要读取的行数,注意它是“预估”,和实际返回行数不一定相等。
当发现 rows 数值异常大,而实际业务只需要少量数据时,往往意味着缺少合适的索引或者统计信息过期。在 MySQL 5.7 中可以用 ANALYZE TABLE 来更新表的统计信息,帮助优化器做出更好的选择。同时结合 Extra 列的 Using where、Using index 等提示,能进一步确认是否发生了回表。
三、通过案例看优化思路
1. 缺失索引导致全表扫描
假设 orders 表有百万级数据,但 status 字段没有索引,执行下面的语句时 Explain 的 type 就会是 ALL。
EXPLAIN SELECT * FROM orders WHERE status = 1;
此时可以在 status 上建立单列索引,或者根据常用查询建立包含 status 和其他过滤字段的联合索引。建立索引后再次 Explain,通常会看到 type 变为 ref 或 range,rows 明显下降。需要注意的是,索引也不是越多越好,写操作会带来维护开销。
另外如果查询里使用了函数包裹字段,比如 WHERE YEAR(create_time) = 2023,即便 create_time 有索引也无法命中。MySQL 5.7 不支持函数索引(8.0 才支持),所以应当改写为范围查询,让优化器能用到普通 B+Tree 索引。
2. 联表顺序与驱动表选择
在多表 JOIN 时,Explain 的 rows 和表顺序暗示了优化器选谁做驱动表。一般小表驱动大表更高效。如果发现大表被放在了前面且 type 不佳,可以检查关联字段的索引情况,或者利用 STRAIGHT_JOIN 强制指定顺序,但此举需谨慎,要先对比执行时间。
EXPLAIN SELECT * FROM users u STRAIGHT_JOIN orders o ON u.id = o.user_id WHERE u.city = 'beijing';
上述写法强制 users 作为驱动表,如果 users 经过 city 过滤后只剩少量记录,那么拿这些 id 去 orders 里通过 user_id 索引查找就会非常快。通过前后两次 Explain 的对比,我们能清楚看到驱动表变化带来的 rows 差异。
四、Extra 列的隐藏信息
Extra 列经常给出关键细节。Using index 表示覆盖索引,不需要回表,性能很好;Using where 表示在取得数据后还做了过滤;Using temporary 和 Using filesort 则通常意味着排序或分组没用到索引,是大查询里需要重点关注的性能杀手。
例如一个带 ORDER BY 的查询如果 Extra 出现 Using filesort,说明排序无法利用索引顺序,只能在内存或磁盘做额外排序。此时应考虑把排序字段加入索引,或者减少排序数据量。MySQL 5.7 的 Explain 不会直接告诉你“慢”,但这些字眼就是优化入口。
五、总结与实践建议
掌握 MySQL 5.7 的 Explain 并不需要背下所有文档,核心是先看 type 排除全表扫描,再看 key 确认索引生效,最后用 rows 和 Extra 评估代价。日常写复杂 SQL 时,养成先 Explain 再上线的习惯,能避免很多线上慢查询事故。
建议把高频接口的核心语句都跑一遍 Explain,建立简单的巡检清单。当数据量增长或业务变动导致索引失效时,执行计划会第一时间露出破绽,比盲目加硬件更省钱也更治本。