EXPLAIN 输出的 key、key_len、rows 字段到底在反映什么
EXPLAIN不是执行器日志,而是优化器生成的「预估执行计划」。它不运行 SQL,只模拟优化器如何选择索引、估算扫描行数、决定连接顺序。其中:
key显示最终被选用的索引名——但注意,它可能是
NULL(没走索引),也可能是
PRIMARY(走了主键),还可能是
idx_name(走了二级索引)。如果
key和
WHERE条件字段不匹配,大概率是索引失效或统计信息过期。
key_len表示实际用到的索引字节数,不是字段长度。比如
VARCHAR(255)定义为 utf8mb4,单字符最多 4 字节,但若条件是
WHERE name = 'abc',且
name是联合索引第一列,则
key_len可能是
12(3 字符 × 4 字节),而不是 1020(255×4);若条件含前缀模糊匹配(
LIKE 'abc%'),
key_len仍会计算,但
LIKE '%abc'就不会走索引,
key_len为
NULL。
rows是优化器基于表统计信息(
INFORMATION_SCHEMA.STATISTICS或
ANALYZE TABLE结果)估算的扫描行数,不是精确值。当
rows远大于实际结果集(比如
SELECT COUNT(*)只返回 10 行,但
rows=98765),说明统计信息陈旧,需手动执行
ANALYZE TABLE t_name更新。
type=ALL 和 type=range 的区别不只是“全表扫 vs 索引扫”
type字段反映访问类型,但它的取值逻辑依赖于「是否用上索引」+「是否能做范围裁剪」+「是否覆盖查询所需字段」:
type=ALL:表示全表扫描,哪怕加了
WHERE条件,只要没命中可用索引(如函数包裹字段:
WHERE YEAR(create_time) = 2023),就会回退到
ALL。
type=range:表示用了索引做范围扫描,但只适用于 B+Tree 索引的有序特性。例如
WHERE id BETWEEN 100 AND 200、
WHERE status IN (1,2)、
WHERE name > 'zhang'。注意:如果联合索引是
(a,b,c),而条件只有
WHERE b = 2,则无法利用该索引的有序性,
type仍是
index或
ALL,不是
range。 容易忽略的一点:
type=index表示遍历整个索引树(按索引顺序全扫),比
ALL快(因索引更小、顺序 I/O 更好),但如果
SELECT *且索引非覆盖,仍要回表,性能未必优于
ALL(尤其当数据页缓存差时)。
Extra 字段里 “Using filesort” 和 “Using temporary” 意味着什么
这两个提示直接暴露了排序和分组操作未走索引,而是由 server 层临时处理:
Using filesort:不是真写磁盘文件,而是 MySQL 在内存中用
qsort排序,超限后才落磁盘。触发常见场景包括:
ORDER BY字段不在索引最左前缀中、
ORDER BY与
WHERE条件用不同索引、
ORDER BY a DESC, b ASC(混合方向,MySQL 8.0 前不支持索引满足)。
Using temporary:表示创建了内部临时表,通常出现在
GROUP BY、
DISTINCT、
UNION或某些
ORDER BY场景。如果
GROUP BY字段没索引,或索引不能覆盖
SELECT列(导致无法用松散索引扫描),就必现此提示。MySQL 5.7+ 默认用
TempTable引擎(内存哈希表),但一旦超出
tmp_table_size或含大字段(
TEXT/BLOB),就会转成磁盘
MyISAM表,性能断崖下跌。 二者常同时出现:
GROUP BY x ORDER BY y若
x和
y不在同一个索引里,既建临时表又额外排序。
为什么 ANALYZE TABLE 后 EXPLAIN 结果变了,但查询没变快
更新统计信息能让优化器选更合理的执行路径,但不保证性能提升——因为优化器只看成本模型,不看真实 I/O 延迟或并发争用:
统计信息准确了,优化器可能从走idx_a改为走
idx_b,但如果
idx_b的叶子节点更稀疏(比如高基数字段 + 高碎片率),实际随机 I/O 次数反而上升。 优化器默认假设所有数据页都在 buffer pool 中,但生产环境常有冷数据。此时
rows=1000的
range扫描,若对应 1000 个分散的磁盘页,比
rows=5000的
ALL扫描(顺序读)更慢。 真正要验证效果,得用
SELECT ... INTO DUMPFILE或
sys.schema_table_statistics查看实际
innodb_rows_read和
handler_read_next,而不是只盯
EXPLAIN的
rows。
EXPLAIN FORMAT=TRADITIONAL SELECT u.name, o.amount FROM users u JOIN orders o ON u.id = o.user_id WHERE u.status = 1 AND o.created_at > '2024-01-01' ORDER BY o.amount DESC;
这类 JOIN 查询的执行计划,优化器决策链极长:先评估单表访问方式(
u.status是否有索引?
o.created_at是否有索引?),再估算 JOIN 代价(NLJ / BKA / Hash Join),最后判断
ORDER BY能否复用某个索引的物理顺序。任何一个环节的统计偏差或隐式类型转换(比如
user_id是
BIGINT,但
ON u.id = o.user_id中某边被转成
DOUBLE),都可能让最终计划偏离预期。
