MySQL EXPLAIN 怎么看:type、key、rows 和 Extra 先抓重点

4次阅读
没有评论

排查慢 SQL 时,很多人会先把 SQL 复制出来,加一个 EXPLAIN,然后被一排字段劝退。其实日常优化不需要一开始就背完整字段表,先抓住几个会直接影响判断的列就够了:typepossible_keyskeyrowsExtra

这篇只整理一条主线:拿到一条执行计划以后,怎样快速判断它是否走了合适的索引、扫描范围是否可控,以及有没有明显的临时表、排序或全表扫描风险。

EXPLAIN 先解决什么问题

EXPLAIN 用来查看 MySQL 计划怎样执行一条语句。最常见用法是在查询前面加上它:

EXPLAIN SELECT *
FROM user_info
WHERE id = 100;

在 MySQL 5.6 之后,它也可以用于查看 DELETEINSERTREPLACEUPDATE 等语句的执行计划。但实际工作里,最常见的还是先从 SELECT 和慢查询开始。

执行计划不是最终真实执行日志,它是优化器给出的计划和估算。也就是说,rows 是估算值,key 是优化器选择的索引,Extra 是补充说明。它们不等于真相的全部,但足够帮我们找到大多数 SQL 的问题方向。

先看 type:访问方式是否太重

type 表示 MySQL 访问表的方式。粗略地说,它越靠近全表扫描,越需要警惕。

常见顺序可以这样记:

  • system / const:基本只会命中一行,通常来自主键或唯一索引等值查询。
  • eq_ref:关联查询里,被驱动表通过主键或唯一索引最多匹配一行。
  • ref:使用普通索引匹配多个可能行,是很常见的索引访问。
  • range:使用索引做范围扫描,例如 BETWEEN><IN
  • index:扫描整棵索引,比全表扫描轻一些,但仍然可能很多。
  • ALL:全表扫描,数据量大时优先排查。

看到 ALL 不一定马上建索引。小表、字典表、结果集本来就要扫全表时,ALL 也可能可以接受。真正要紧的是:这张表是否很大,这条 SQL 是否高频,过滤条件是否应该命中索引。

一个更实用的判断是:核心业务表、高频接口、分页列表和后台筛选,如果 type=ALLrows 很大,就应该继续往下查索引和条件写法。

possible_keys 和 key:有没有索引,最后用了哪个

possible_keys 表示优化器认为可能用到哪些索引,key 表示最后实际选择了哪个索引。

常见情况有三种:

  1. possible_keys 为空,key 也为空:说明当前条件下没有合适索引。
  2. possible_keys 有值,但 key 为空:有候选索引,但优化器判断不用更划算。
  3. key 有值:实际走了某个索引,接下来要看它是不是你期望的那个。

如果你明明建了组合索引,但 key 没有使用它,常见原因包括:

  • 查询条件没有满足最左前缀。
  • 对索引列做了函数、表达式或隐式类型转换。
  • 条件选择性太差,优化器认为扫描全表更便宜。
  • 统计信息不准,可以考虑分析表或观察真实执行情况。
  • 组合索引顺序和查询模式不匹配。

不要只看到 key 有值就放心。有时 SQL 的确用了索引,但走的是低选择性的单列索引,扫描仍然很大;也有时出现 index_merge,说明多个单列索引被合并使用,如果这类 SQL 很频繁,可能需要重新设计组合索引。

key_len 和 ref:辅助判断命中了索引的哪部分

key_len 表示 MySQL 使用到的索引长度。它可以帮助判断组合索引到底使用到了几列。

比如你有一个组合索引:

KEY idx_user_status_time (user_id, status, created_at)

如果查询条件只命中了 user_idkey_len 通常会比较短;如果同时命中了 user_idstatus,长度会更长。它不是最直观的字段,但在排查“组合索引是不是只用了一半”时很有用。

ref 则表示索引列拿什么来匹配,可能是常量,也可能是前一张表的某个字段。它常用于关联查询里判断 join 条件是否通过索引连接。

日常阅读可以先不死背每种类型的字节数,只把它当成一个验证信号:如果组合索引没有按预期使用,key_len 往往能提供线索。

rows:估算扫描行数是否过大

rows 是优化器估算需要扫描的行数。它不是精确值,但非常值得看。

如果 type 还可以,但 rows 很大,说明 SQL 仍然可能很重。例如:

  • type=range,但范围太宽,扫描几十万行。
  • type=ref,但普通索引选择性太差,匹配大量记录。
  • type=index,扫完整个索引,只是没有回表而已。

优化时可以按这个顺序问:

  1. 过滤条件是否足够收敛。
  2. 索引列顺序是否贴合查询条件。
  3. 是否需要把排序字段、筛选字段放进同一个组合索引。
  4. 分页是否使用了过深的 OFFSET
  5. 是否应该拆查询、先查主键再回表,或者改成游标分页。

不要只看 SQL 里有没有 LIMIT。深分页里,即使最后只返回 20 行,MySQL 也可能为了跳过前面的结果扫描很多行。

Extra:重点盯住几个高频提示

Extra 里会出现很多补充信息,先记住几个高频项就能解决大部分问题。

Using index 表示使用覆盖索引,不需要回表读取完整行。通常这是好信号,但也要配合 rows 看扫描量。

Using where 表示存储引擎取到记录后,还要在 MySQL 层继续过滤。它不一定是坏事,但如果同时伴随 ALL 或很大的 rows,就要警惕。

Using temporary 表示需要临时表,常见于 GROUP BYDISTINCT、复杂排序或派生表场景。数据量大时要重点看。

Using filesort 表示 MySQL 不能直接利用索引完成排序,需要额外排序。它不一定真的落磁盘,但说明排序没有被索引顺序自然满足。

Impossible WHERE 表示条件永远不成立,通常是 SQL 条件写错或优化器判断可以直接返回空结果。

日常最常见的优化对象是 Using temporaryUsing filesort 同时出现,尤其在列表页、报表页和后台筛选里。解决方向通常是调整组合索引、减少排序字段、拆查询或改变分页方式。

子查询和派生表先看 select_type

select_type 可以帮助理解查询层级。常见的有:

  • SIMPLE:没有子查询或 UNION 的简单查询。
  • PRIMARY:最外层查询。
  • SUBQUERY:子查询。
  • DEPENDENT SUBQUERY:依赖外层查询的子查询。
  • DERIVED:FROM 子句里的派生表。
  • UNION / UNION RESULT:UNION 相关查询。

如果看到 DEPENDENT SUBQUERY,要特别留意。它往往表示子查询依赖外层行,外层每处理一行,内层都可能执行一次。数据量大时,这类写法容易变成性能问题。

处理思路不一定是“所有子查询都改 JOIN”。更稳妥的是先看执行计划和数据量:如果子查询独立、结果集小、索引清楚,未必有问题;如果依赖外层、扫描量大,就要考虑改写。

一条实用阅读顺序

拿到一条 EXPLAIN,可以按下面顺序看:

  1. 先看 tableid,确认执行计划里每一行对应哪张表、哪个查询层级。
  2. type,判断访问方式是不是已经接近全表扫描。
  3. possible_keyskey,确认有没有候选索引、实际用了哪个索引。
  4. key_len,判断组合索引是否按预期命中。
  5. rows,判断扫描量是否可接受。
  6. Extra,重点找 Using temporaryUsing filesortUsing where
  7. 如果是多表查询,再看驱动顺序和被驱动表是否走索引。

这个顺序足够应付大多数日常慢 SQL 初筛。先不要陷入所有字段的细节,先判断“有没有扫太多、有没有排太多、有没有临时表、索引用得对不对”。

小结

EXPLAIN 的核心不是背字段,而是把 SQL 的执行路径看清楚。

日常先抓这几个信号:type 看访问方式,key 看实际索引,key_len 看组合索引用了多少,rows 看扫描量,Extra 看临时表、排序和覆盖索引。

如果一条 SQL 的 type 太差、rows 太大、key 不符合预期,或者 Extra 里出现明显的临时表和额外排序,就值得继续优化。反过来,如果访问方式合理、扫描量可控、排序可以被索引覆盖,就没必要为了追求“看起来更高级”的执行计划而过度改写。

正文完
 0
bdspAdmin
版权声明:本站原创文章,由 bdspAdmin 于2026-06-15发表,共计3240字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)