用EXPLAIN看懂MySQL索引怎么加

聊聊怎么用EXPLAIN读懂MySQL的执行计划,type、key、rows、Extra这几个关键字段说明了啥问题,再讲讲我自己加索引时的几条原则:主键必加、高频查询字段优先、联合索引遵循最左前缀,配着真实SQL案例过一遍。

之前排查一个慢查询问题,同事甩过来一句“你加个索引不就好了”,说得轻巧,但加在哪个字段、加单列还是联合索引,其实是有讲究的。

瞎加索引不仅没用,还可能拖慢写入速度,占用额外存储空间。

今天我们从 EXPLAIN 命令入手,看看怎么读懂 MySQL 的执行计划,再聊聊我自己平时加索引时会遵循的几条原则。

EXPLAIN 是干什么的

EXPLAIN 命令能让 MySQL 告诉我们,一条 SQL 语句实际是怎么被执行的:走没走索引、走了哪个索引、预估要扫多少行数据。

打个比方,EXPLAIN 就像导航软件的路线规划页面,实际开车之前先看一眼,是不是要绕远路、路上有没有拥堵路段,而不是直接一头开进去才发现堵车。

用法很简单,直接在 SQL 前面加一个 EXPLAIN:

-- 老王的订单表,按用户ID查订单
EXPLAIN SELECT * FROM orders WHERE user_id = 1001;
​

执行完会返回一张结果表,里面有好几个字段,我们重点看这几个:

type 字段:这次查询走的是什么类型

type 表示 MySQL 找数据用的访问方式,从好到差大致是这个顺序:

  • system / const:常量级别,比如按主键或唯一索引查单条记录,最快。
  • eq_ref:多表关联时用主键或唯一索引等值匹配,效率很高。
  • ref:普通索引的等值查询,命中了索引但不是唯一的。
  • range:范围查询,比如 WHERE age > 20 用上了索引。
  • index:扫的是整个索引树,但没有用上具体的过滤条件。
  • ALL:全表扫描,最差的情况,基本等于没走索引。

如果看到 type 是 ALL,基本可以确定这条 SQL 没用上合适的索引,得回头看看是不是索引没建对,或者查询条件让索引失效了。

key 字段:实际用的哪个索引

key 告诉我们这次查询实际命中的索引名字,如果是 NULL,说明压根没走索引。

有时候我们明明建了索引,key 却是 NULL,这种情况往往是索引失效了,比较常见的原因后面会讲。

rows 字段:预计扫描的行数

rows 是 MySQL 预估这次查询大概要扫多少行数据才能拿到结果,这个数字越小说明查询效率越高。

如果一张表几十万行数据,rows 显示要扫十几万行,那基本可以判断索引没有起到该有的过滤作用。

Extra 字段:一些额外提示信息

Extra 里经常会出现几个值得注意的关键词:

  • Using index:表示这次查询直接从索引里就能拿到所有需要的数据,不用回表查原始数据,效率很高,这种叫“覆盖索引”。
  • Using filesort:表示排序不能用索引完成,MySQL 需要额外做一次排序操作,数据量大的时候会比较慢。
  • Using temporary:表示用到了临时表,通常出现在 GROUP BY 或者复杂的多表关联查询里,也是性能隐患的信号。

看到 Using filesort 和 Using temporary,一般就要考虑是不是排序字段、分组字段也该加进索引里了。

一个真实案例走一遍

假设我们有张订单表,结构大概是这样:

CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    status TINYINT NOT NULL,
    created_at DATETIME NOT NULL
);
​

没加任何额外索引之前,查某个用户某个状态的订单,按创建时间排序:

EXPLAIN SELECT * FROM orders 
WHERE user_id = 1001 AND status = 1 
ORDER BY created_at DESC;
​

这时候 type 很可能是 ALL,key 是 NULL,Extra 里还带着 Using filesort,说明这条 SQL 全表扫描加上额外排序,数据量一大肯定慢。

给它加一个联合索引试试:

-- 按查询条件的常见组合建联合索引
ALTER TABLE orders ADD INDEX idx_user_status_created (user_id, status, created_at);
​

再执行一次 EXPLAIN,这次 type 变成 ref 或者 range,key 显示用上了 idx_user_status_created,Extra 里的 Using filesort 也很可能消失了,因为索引本身已经是按 created_at 排好序的。

是不是感觉查询计划一下就清爽了很多?这就是索引该起的作用。

我自己加索引的几条原则

结合平时踩过的坑,总结几条自己加索引时会遵循的原则:

主键优先用自增整数

每张表都应该有主键,InnoDB 的主键本身就是聚簇索引,数据物理存储顺序跟主键顺序一致。用自增整数当主键,插入的时候数据是顺序写入的,不会引发频繁的页分裂;如果用 UUID 这类无序的值当主键,插入位置随机,容易导致索引页频繁分裂,性能会打折扣。

高频查询字段优先加索引

不是所有字段都要加索引,优先看 WHERE、ORDER BY、GROUP BY 里出现频率最高的字段。像状态字段、创建时间、用户 ID 这类经常被用来筛选或排序的字段,是加索引的第一梯队。

反过来,如果一个字段的取值区分度很低(比如性别字段只有男女两种值),加索引的收益就很小,因为不管走不走索引,扫描的数据量差别不大。

联合索引要遵循最左前缀原则

这条应该是最容易被忽略的一条。联合索引 (a, b, c),实际生效的场景是:条件里用到了 a,或者 a 和 b,或者 a、b、c 全部用到,但如果条件只有 b 或者只有 c,压根用不上这个索引。

举个例子,我们前面建的 (user_id, status, created_at) 索引:

-- 能用上索引:条件从user_id开始,符合最左前缀
WHERE user_id = 1001;
WHERE user_id = 1001 AND status = 1;

-- 用不上索引:跳过了最左边的user_id
WHERE status = 1;
WHERE created_at > '2024-01-01';
​

所以设计联合索引的时候,字段顺序要按照查询里出现的先后、区分度高低来排,通常把等值查询条件放前面,范围查询条件放后面。

别为了加索引而加索引

索引不是越多越好,每加一个索引,写入(INSERT/UPDATE/DELETE)的时候都要多维护一份索引结构,索引太多反而会拖慢写入性能,还占用额外的磁盘空间。

一般的做法是先把慢查询日志(slow_query_log)跑起来,看真正慢的 SQL 长什么样,再针对性地用 EXPLAIN 分析,缺什么索引补什么,而不是把所有可能用到的字段都无脑建上索引。

小结

EXPLAIN 是我们诊断 SQL 性能问题最直接的工具,type 看访问方式,key 看用了哪个索引,rows 看扫描行数,Extra 看有没有额外的排序或临时表开销。

加索引这件事,说白了就是拿写入性能和存储空间去换查询效率,加在高频查询、区分度高的字段上,联合索引记得对齐查询条件的最左前缀,别贪多。

很多同学加索引全靠“感觉”,从来不跑一下 EXPLAIN 确认效果,加完之后到底有没有生效自己都说不清楚,这个习惯建议改一改。

那关于 EXPLAIN 和索引优化就先讲到这里,自行拿手头的慢 SQL 跑一遍 EXPLAIN 练练手吧。

更多推荐

章节目录