EXPLAIN中type=ALL、key=NULL或rows接近总行数均表明全表扫描;key_len可判断联合索引实际使用层数;type看似正常但rows过大或选择性差,同样导致性能崩溃。

索引失效不等于一定全表扫描,但 EXPLAIN 中出现 type=ALL 就是典型信号——MySQL 放弃了所有二级索引,直接遍历聚簇索引(即整张表)。
怎么看 EXPLAIN 里是否真走了全表扫描
关键就盯三个字段:type、key、rows
-
type=ALL:明确表示全表扫描,不管有没有索引,优化器认为扫全表更便宜 -
key=NULL:哪怕type不是ALL(比如是index),只要key是NULL,说明没用上你建的任何索引 -
rows接近表总行数:比如表有 100 万行,rows=982341,基本等价于全扫——即使type显示range或ref,也说明索引覆盖范围过大,失去意义
为什么 type=ALL 不代表“没建索引”,而是“索引被绕过了”
MySQL 优化器基于 cost 决策,不是“有索引就一定用”。常见绕过原因:
- 查询条件跳过了联合索引最左列,比如索引是
idx_name_age,却只写WHERE age=25 - 对索引列用了函数,如
WHERE DATE(create_time)='2026-04-15'→ 索引存的是原始DATETIME,不是日期截断值 - 隐式类型转换,如
phone是VARCHAR,却写WHERE phone=13800001111→ MySQL 暗中转成CAST(phone AS SIGNED),破坏索引匹配 - 范围查询后还有等值条件,如索引
(a,b,c),写WHERE a=1 AND b>10 AND c='x'→c列索引失效(B+树中b>10后c无序)
EXPLAIN 的 key_len 值暴露了索引用了几层
key_len 不是“索引长度”,而是“本次查询实际用到的索引字节数”。它能帮你反推是否遵守了最左前缀:
- 假设
idx_name_age_city是(name VARCHAR(50), age TINYINT, city VARCHAR(20)) -
key_len=53:说明只用了name(50 字节 + 2 字节变长长度标识 + 1 字节 NULL 标志)→age和city没参与 -
key_len=54:说明用到了name和age(50+2+1+1),city仍被跳过 - 如果
key_len比预估小很多,大概率是中间某列因范围查询或类型不匹配被截断了
别只信 type=ref 或 range,要看 rows 是否失控
有些场景下 type 看似健康,但性能已崩:
-
type=range+rows=800000(表共 100 万行):本质就是“带范围的全扫”,比ALL更隐蔽 -
type=ref+key=idx_status+rows=50000:如果status只有 3 个取值(如 'pending'/'done'/'error'),那这个索引选择性太差,优化器本就不该依赖它 -
type=index:表示走的是索引树全遍历(index scan),不是全表扫描,但若Extra里带Using where,说明还得回表过滤,I/O 成倍增加
真正危险的不是 type=ALL 这个字面意思,而是 rows 和 key_len 共同揭示的“索引形同虚设”——它可能正在默默拖垮你的慢查询。


















