type 是 EXPLAIN 里最重要字段之一(访问类型),代表 MySQL怎么去查找行,性能从最好 → 最差依次排列,后端面试高频考点,我给你完整版本、含义、例子、踩坑、面试话术(适合求职)。
⚠️口诀: system > const > eq_ref > ref > range > index > ALL
越靠左越快;目标:尽量做到 ref / range ;坚决警惕 ALL (全表扫描)
逐个详解(带示例 + 要点 + 坑)
表约定:
user(id主键, phone唯一索引, name普通索引, age无索引)
- system ⭐最优
表只有 1 行数据,系统表也常见。
- 极少见到,只是理论最高等级;业务SQL几乎碰不到。
sql
EXPLAIN SELECT * FROM mysql.time_zone; --系统表经常 system
- 注意:不是你优化出来的,是表本身就只有一条,不用刻意追求。
- const ⭐非常好
通过主键 / 唯一索引等值匹配,最多返回1行;查到之后这个值被当成「常量」,优化器后续直接用,不再回表扫描。
sql
EXPLAIN SELECT * FROM user WHERE id = 100; -- id主键
EXPLAIN SELECT * FROM user WHERE phone='13800138000'; -- phone唯一索引
✅特征:等值、唯一索引、最多一行
坑: where id>1 →就不是 const!const 必须是等值 =。
- eq_ref ⭐很好
多表 JOIN 时:被驱动表用主键 / 唯一索引去匹配,每次连接只匹配到一行。
常见: A JOIN B ON B.id = A.b_id ,B 的 id 是主键。
sql
EXPLAIN SELECT * FROM a JOIN b ON b.id = a.b_id;
-- b那一行的 type=eq_ref
区别:
-
const :单表,查一次就结束
-
eq_ref :join循环里,每一次都精准命中唯一一条
注意:普通索引不会出来 eq_ref,必须主键/唯一。
- ref ✅理想常用(业务优化目标之一)
使用普通二级索引等值查询,可能匹配多行。不是唯一,所以叫「引用查找」。
sql
EXPLAIN SELECT * FROM user WHERE name='张三'; -- name普通B+索引
EXPLAIN SELECT * FROM user WHERE age=20; -- 如果age建普通索引
也出现在 join:被驱动表用普通索引匹配多条。
✅日常标准:很多正常优化完的SQL就是 ref,不用非要冲到 const。
坑:ref 只是代表用到索引;不代表就快! 如果这个值命中几十万行,照样慢;要看 rows 列。
补充: ref_or_null (ref变种): where name='xxx' or name is null ,也是索引可用。
- range ⚠️有索引但是「范围扫描」
在索引上做范围查找: >、<、>=、<=、between、in()、or
在索引的一段区间来回扫,而不是精准一个点。
sql
EXPLAIN SELECT * FROM user WHERE id > 100;
EXPLAIN SELECT * FROM user WHERE id BETWEEN 1 AND 500;
EXPLAIN SELECT * FROM user WHERE phone IN ('a','b');
⭐超级重要考点:
一旦 type=range ,并且这个字段在联合索引里 →它就是「范围断点」!它后面的 order by 字段再也不能利用索引消除 filesort (就是上一节讲 filesort 的那个坑!)
注意: in 有时候优化器判定为 ref,有时候 range,取决于数量。
range 不是不能用;但是扫描区间太大也要警惕,看 rows 。
- index ⚠️「全索引扫描」!≠ 好东西!
把整个B+索引从头到尾扫一遍(索引树遍历,没有利用索引快速定位)
注意!很多人误解:type=index就是走了索引、很OK ❌
-
和 ALL(全表)区别:
-
ALL :扫描聚簇索引(完整数据行)
-
index :扫描二级索引树;IO通常比ALL小,但依然是完整遍历索引!
两种典型场景:
-
select name from user; name有二级索引 →覆盖索引但是没有where条件,直接把二级索引全部扫一遍(Extra:Using index)
-
order by name ,没有where;被迫顺序扫整个索引用来排序
不要看见 type=index 就开心:它是「索引上的全表扫描」,数据量大依然灾难。
- ALL ❌最坏!全表扫描(Full Table Scan)
没可用索引,遍历聚簇索引全部行。就是大家常说的没走索引。
sql
EXPLAIN SELECT * FROM user WHERE age=18; -- age没建任何索引
但是有个重要理性认知:
不是看到 ALL 就必须立刻加索引!
-
如果表只有几十行,全表扫描反而更快(索引还有回表开销),优化器会主动选择 ALL;
-
如果表几千、几万+、QPS高 → ALL就是危险信号,必须干预。
还有几个少见扩展type(面试偶尔提到)
-
ref_or_null :ref + 还要找 NULL,例如 where name='xx' or name is null
-
unique_subquery :子查询里命中唯一索引
-
index_subquery :子查询命中普通索引
-
merge :索引合并( index_merge ,Extra会提示)多个单字段索引合并一起用;有时候是临时救急,通常不如精心设计一条联合索引
index_merge:比如 where a=1 or b=2 ,a,b各有单独索引;MySQL把两个索引结果合并;它不等于「优化完美」,很多时候代表缺少合适联合索引。
📊性能等级速查表(复制备忘)
type 等级 说明 目标?
system ✨天花板 仅单行系统表 不用追求
const ✨极好 主键/唯一索引等值,1行 能拿到当然棒
eq_ref ✅优秀 Join,主键精准单行匹配 正常
ref ✅良好 普通索引等值,多条 ✅业务最常见理想结果
range ⚠️注意 索引范围扫描 > < between in 可用,提防「范围阻断order by」
index ❗警惕 全索引扫描 尽量避免(虽然是索引但在遍历)
ALL ❌危险 全表扫描 大表必须消灭
一条记忆链条(背这个面试直接说):
system > const > eq_ref > ref > range > index > ALL
🧠面试高频问题 & 口述答案(背)
Q1: type=index 是不是代表已经很好的走索引了?
A:不是!type=index = 全索引扫描,它只是遍历二级B+树,没有利用索引快速定位;相当于"索引版全表扫描",量大依然很慢,不要和ref/range混淆。
Q2: ref 和 range 的区别?
A:ref是索引等值点查找;range是一段区间扫描;range有一个很关键副作用:在联合索引中它是范围断点,后面排序字段无法利用索引消除filesort。
Q3:是不是只要 type=ALL 我就要马上加索引?
A:不一定,如果表很小(几十行),优化器认为全表扫描成本更低;但是大表、高频查询出现ALL就必须优化;同时还要配合看 rows 、 key 、 Extra(Using filesort/Using temporary) 一起判断,不能单拿type一个字段下定论。
Q4:index_merge是什么?好不好?
A:是索引合并,多个独立索引分别查找再合并;它有时是优化器妥协方案,多数场景一条设计良好联合索引性能会优于index_merge。
✅配套看Explain其他列(和type联动)
不要只看type!要组合判断:
-
key :真正被选用的索引名字(null=没选)
-
key_len :这次索引用到多少字节(用来判断联合索引用了几列!超级实用调参工具)
-
rows :预估扫描行数(type再好,如果rows几十万也要小心)
-
Extra :有没有 Using filesort / Using temporary
如果你需要,我可以给你: key_len怎么计算 + 几个「同一条SQL,改索引→type从ALL→ref→range变化的演示案例」;或者一份Explain完整字段速查表。