当前位置: 代码网 > it编程>数据库>Mysql > MySQL索引优化之EXPLAIN的零基础完全精通指南

MySQL索引优化之EXPLAIN的零基础完全精通指南

2026年09月17日 Mysql 我要评论
核心观点:explain 是索引优化的"眼睛"。不会看 explain,索引知识就只能靠背——比如"联合索引用了几列",背下来也验证不了

核心观点:explain 是索引优化的"眼睛"。不会看 explain,索引知识就只能靠背——比如"联合索引用了几列",背下来也验证不了。

一、explain 是什么

一句话:它不执行 sql,只让优化器把"打算怎么执行这条 sql"打印出来。

相当于执行前的"作战计划"。

能解决什么问题

  • 验证索引有没有生效
  • 看联合索引实际用到了第几列
  • 找出全表扫描、回表、额外排序、临时表
  • 对比优化前后,量化收益

基本用法

explain select user_id, amount from orders where user_id = 13;

四种输出格式

格式命令用途
传统表格explain日常快速看
jsonexplain format=json成本估算 cost,信息最全
treeexplain format=tree树状展示执行顺序
真实执行explain analyze真跑一遍,给实际耗时(8.0.18+)

二、看 explain 的正确顺序(30 秒定位问题)

不要从左往右逐字段读,按这个顺序扫:

  • type        走没走索引?          ← 先看有没有大问题
  • key         走的哪个索引?
  • key_len     用了几列?            ← 联合索引的关键
  • rows×filtered   大概要处理多少行?
  • extra       有没有回表/排序/临时表?

口诀:先看 type 定生死,再看 key_len 定列数,最后看 extra 定细节。

三、逐字段详解

先给一张完整字段总表(8.0 传统格式):

字段含义
id查询序号,越大越先执行;相同则从上往下
select_type查询类型:simple / primary / subquery / derived / union
table正在访问的表
partitions命中哪些分区(分区表才有意义)
type访问类型(索引使用等级)★
possible_keys候选索引
key实际使用的索引
key_len实际用到的索引字节数 ★★
ref与索引列比较的对象(const / 列名)
rows预估扫描行数
filtered过滤后剩余百分比
extra额外执行信息 ★

3.1 select_type

含义
simple简单查询,不含子查询 / union
primary最外层查询
subquery子查询(不在 from 里)
derivedfrom 里的子查询(派生表)
unionunion 中第二个及以后的 select
union resultunion 的结果集

出现 derived 说明"from 里套了子查询",mysql 会建临时表——能用 join 改写就改写。

3.2 type:访问类型(等级从好到坏)★

type含义典型场景
system表只有一行系统表
const主键 / 唯一索引等值,最多 1 行where id = 1
eq_refjoin 时被驱动表用主键 / 唯一索引关联字段是主键
ref普通索引等值查询where user_id = 13
range索引范围扫描between / > / < / in
index扫整棵索引树覆盖索引但全扫
all全表扫描无索引 / 索引失效

记忆要点

  • ref / range 是目标
  • all 必须优化
  • index 是"伪装成索引的全表扫描"——扫的是索引树,但仍遍历所有节点,数据量大时一样慢

关键区分indexall 都是全扫,区别只是"扫索引树"还是"扫数据表"。若 extra = using index,说明走了覆盖索引,只需扫索引即可返回,比 all 好,但仍不如 ref / range

3.3 key_len:联合索引"用了几列"的唯一证据 ★★

定义:这条 sql 实际用到的索引列的总字节数

为什么重要type=ref 只说明"走了索引",但联合索引 (a,b,c) 走到了第几列?只有 key_len 能回答。这是判断"最左前缀用到哪、后面列有没有白建"的唯一量化依据。

计算公式(三个加项)

加项规则
类型基础长度见下表
变长类型varchar 额外 +2(存长度)
可空列额外 +1

类型基础长度

类型字节
tinyint1
smallint2
mediumint3
int4
bigint8
float4
double8
date3
time3
datetime5(5.6+)
timestamp4
char(n)n × 字符集单字符字节数
varchar(n)n × 字符集单字符字节数 + 2

字符集单字符最大字节utf8mb4 = 4,utf8(utf8mb3) = 3,gbk = 2,latin1 = 1。

手算示例

create table t (
  a int not null,
  b int null,
  c varchar(10) not null,
  index idx_abc (a, b, c)
) engine=innodb default charset=utf8mb4;

逐列算:

计算过程字节
aint not null4
bint null → 4 + 15
cvarchar(10) utf8mb4 → 10×4 + 242

所以:

实际用到的列key_len
只用 a4
用 a + b9
用 a + b + c51

反向读法:看到 key_len = 4 → 只用了 a;看到 9 → 用了 a、b;看到 51 → 三列全用上。

实战读法(key_len 的真正价值)

拿到一条 sql,先算"全部列都用上应该是多少",再对比实际 key_len,差值就是"没用上的列"。

注意:key_len 只反映"用于定位(索引查找)的列"。范围查询之后的列不参与定位(key_len 不增加),但如果满足 icp 条件,仍可用于索引层过滤——这时 extra 会出现 using index condition。所以"key_len 没变"不代表后面的列完全没用。

3.4 key / possible_keys

  • possible_keys:候选索引(优化器认为"可能用得上"的)
  • key:最终选中的索引

三种典型情况:

现象含义处理
possible_keys 有值,key 有值正常用了索引检查是不是你期望的那个
possible_keys 有值,key = null优化器算完成本后主动放弃不是失效! 通常是"回表代价 > 全表扫描",考虑覆盖索引
possible_keys = null没有可用索引缺索引,或索引失效(函数 / 类型转换)

3.5 ref

显示"索引列和谁比较":

  • const:和常量比(等值查询)
  • 库名.表名.列名:和另一张表的列比(join)
  • null:不是等值比较

3.6 rows / filtered

  • rows预估扫描行数(基于统计信息,不是精确值)
  • filtered:扫描后剩余百分比

真实代价 ≈ rows × filtered%——这才是要处理的有效行数。

rows 是估算值,可能严重失真。统计信息过期时,explain 会明显偏离实际——这时用 explain analyze 看真实值,或先 analyze table 表名; 刷新统计。

3.7 extra:信息量最大的一列 ★

extra 值含义好坏
using index覆盖索引,免回表
using index condition索引下推 icp(5.6+)
using mrr多范围读优化
using index for group-by分组也走索引
using where拿到数据后还要过滤中性
using join bufferjoin 无索引,用内存缓冲
using filesort需要额外排序
using temporary需要建临时表
null没有额外信息

重点解释三个"坏"值

  • using filesort:索引不能提供 order by 需要的顺序,需额外排序。解决:把排序列放进联合索引(条件列之后),且排序方向一致(8.0 可用降序索引解决混排)。
  • using temporary:常见于 group by / distinct / union 无合适索引。解决:给分组列建索引。
  • using join buffer:被驱动表的关联列没索引。解决:给关联列建索引。

using indexusing where 可以同时出现:前者说"索引覆盖了需要的列",后者说"仍有过滤条件"。这是常见组合,不是矛盾。

四、实战:用 explain 回答"联合索引用了几列"

场景a int not null, b int not null, c int not null,联合索引 (a, b, c)。三列都是 int not null,每列 4 字节。

四条 sql 的 explain 结果对比

#查询条件typekeykey_lenextra生效列
1a=1 and b=2 and c=3refidx_abc123 列
2a=1 and c=3refidx_abc4只用 a
3b=2 and c=3allnullnullusing where0 列
4a between 1 and 5 and b=2 and c=3rangeidx_abc4using index condition只用 a(b、c 仅过滤)

从这张表能读出三条核心结论

  1. key_len 从 12 掉到 4 = 从"3 列"掉到"1 列"。这是最左前缀"跳列截断"的直接证据。
  2. 第 3 条 type = allkey = null = 跳过最左列,整个索引作废(不是"部分生效")。
  3. 第 4 条 key_len = 4 但 extra = using index condition = 范围查询让 a 之后的 b、c 无法用于定位,但 icp 让它们在索引层完成过滤——这就是"范围后失效 ≠ 完全没用"的实证。

动手练习:建一张这样的表,把上面 4 条 sql 各 explain 一次,亲眼看 key_len 从 12 变 4、type 从 ref 变 all。explain 是"看"会的,不是"读"会的。

五、进阶用法

5.1 explain format=json(看成本)

explain format=json select ...;

关键看 cost_info

"cost_info": {
  "query_cost": "12.35",
  "read_cost": "4.20",
  "eval_cost": "0.85"
}

对比两条 sql 的 query_cost,能量化"优化到底有没有用"。也能看到优化器为什么选某个索引(成本估算过程)。

5.2 explain analyze(8.0.18+,看真实耗时)

explain analyze select ...;

会真正执行 sql,输出每个步骤的实际耗时实际行数

-> index lookup on orders using idx_user (user_id=13)
   (cost=0.35 rows=1) (actual time=0.05..0.06 rows=1 loops=1)

关键对比rows=1(估算)vs rows=1(实际)——估算和实际差距大,说明统计信息失真,这是优化器选错索引的常见原因。

注意:explain analyze 会真的执行 sql,不要在写库 / 大表上乱跑

5.3 explain format=tree(8.0+)

树状展示执行顺序,直观看到"先做什么、后做什么"。

六、常见"索引失效"场景速查(配合 explain 验证)

场景explain 表现解法
条件列用函数 where year(create_time)=2026type=allkey=null改成范围 create_time >= '2026-01-01'
隐式类型转换 where phone=13800138000(phone 是 varchar)type=all加引号 phone='13800138000'
前导模糊 like '%王'type=all改后缀匹配,或上 es
or 两侧有一侧无索引type=all给两侧都建索引,或改 union
跳最左列 where b=2(索引是 a,b)type=all补最左列,或另建索引
范围查询之后的列key_len 不增加把等值列放前面
索引列参与运算 where id+1=5type=all改成 where id=4

注意!= / not in / is not null 不必然失效——数据量占比小时优化器可能仍走索引。以 explain 实测为准,不要背结论。

七、面试速记卡

#核心要点一句话记忆
1看 explain 的顺序typekeykey_lenrowsextra
2type 目标值ref / range 是目标,all 要修,index 是伪装的全扫
3key_len 作用判断联合索引实际用了几列
4key_len 算法类型字节 +(varchar +2)+(可空 +1)
5possible_keys 有值但 key=null优化器算完成本主动放弃,不是失效
6extra 三个坏值filesort(排序)、temporary(临时表)、join buffer(无索引)
7覆盖索引extra = using index,免回表
8索引下推extra = using index condition,范围后的列仍可过滤
9估算失真怎么办analyze table 刷新统计,或 explain analyze 看实际
10核心原则索引失效不要背结论,以 explain 实测为准

以上就是mysql索引优化之explain的零基础完全精通指南的详细内容,更多关于mysql explain用法的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com