MySQL的EXPLAIN执行计划与实际执行不一致,因其仅基于过期统计信息静态估算成本,受ANALYZE TABLE未更新、函数导致索引失效、UNION/子查询中间结果偏差、optimizer_switch特性及I/O压力等因素影响。

MySQL 为什么有时 EXPLAIN 显示的执行计划和实际执行不一致
因为 EXPLAIN 默认只做「静态分析」:它不真正执行 SQL,而是基于统计信息、索引结构、查询条件等估算成本,选择它认为最优的访问路径。但估算依赖的行数(rows)、索引基数(cardinality)、数据分布是否倾斜,都可能过期或不准。
常见诱因包括:
-
ANALYZE TABLE没有及时更新,导致优化器误判某索引“更高效” - WHERE 条件含函数(如
WHERE YEAR(create_time) = 2023),索引失效,但EXPLAIN可能仍显示key非空(实为回退到全表扫描) - 使用了
UNION或子查询,EXPLAIN对各分支单独估算,无法反映嵌套执行时的中间结果集大小变化 - 开启了
optimizer_switch='index_merge=on'等高级特性,但实际运行中因锁竞争或 buffer pool 压力导致走不同路径
type 字段值从 const 到 ALL 的真实含义差异
type 描述的是 MySQL 如何定位数据行,不是“快慢等级表”,而是「访问方式的本质区别」。关键不在名字,而在它背后触发的 I/O 行为:
-
const:主键/唯一索引等值匹配,且优化器确认最多一行 → 直接定位磁盘页,无需循环 -
ref:非唯一索引等值查找 → 定位索引 B+ 树叶子页后,需遍历可能的多条记录 -
range:索引范围扫描 → B+ 树中做区间遍历,但不回表(若覆盖索引)或需回表(若未覆盖) -
index:全索引扫描 → 遍历整个索引 B+ 树叶子链表,比ALL快仅因不用读聚簇索引数据页 -
ALL:全表扫描 → 逐行读聚簇索引数据页,最耗 I/O;但若SELECT *且表很小,可能比多次随机回表更快
注意:type=index 不代表用了索引就一定好——如果索引字段少、回表代价高,而聚簇索引本身很紧凑,ALL 反而更优。
无论做任何事情,都要有一定的方式方法与处理步骤。计算机程序设计比日常生活中的事务处理更具有严谨性、规范性、可行性。为了使计算机有效地解决某些问题,须将处理步骤编排好,用计算机语言组成“序列”,让计算机自动识别并执行这个用计算机语言组成的“序列”,完成预定的任务。将处理问题的步骤编排好,用计算机语言组成序列,也就是常说的编写程序。在Pascal语言中,执行每条语句都是由计算机完成相应的操作。编写Pascal程序,是利用Pasca
如何让 EXPLAIN FORMAT=TREE 真正有用
FORMAT=TREE 输出的是优化器最终决策的执行树结构,比传统格式更能暴露「为什么选这个连接顺序」「聚合下推是否发生」。但它默认关闭,且依赖 MySQL 8.0.16+,启用前必须确认:
- 执行
SET optimizer_switch='use_index_extensions=on,condition_fanout_filter=on';否则部分优化细节被隐藏 - 避免在
EXPLAIN中混用用户变量(如@var:=@var+1),会导致FORMAT=TREE降级为传统格式 - 关注输出中的
->缩进层级:最深的节点是实际最先执行的物理操作(如Index lookup),而非 SQL 书写的顺序 - 若看到
Using temporary; Using filesort出现在子节点中,说明该层已触发内存/磁盘临时表,不是外层问题
key_len 值比预期小,是不是索引没用上
不一定。key_len 显示的是 MySQL 实际使用的索引字节数,受字段类型、是否允许 NULL、字符集影响极大。例如:
-
VARCHAR(255)字段建了联合索引(a,b,c),但WHERE a='x' AND b IS NULL→key_len只算a长度,b因为IS NULL不参与索引查找 -
utf8mb4下VARCHAR(10)最大占 40 字节(10×4),但若实际值全是 ASCII,key_len可能只显示 10 -
TINYINT NOT NULL占 1 字节,但加了DEFAULT NULL后,key_len多出 1 字节用于存储 NULL 标志位
真正判断索引是否生效,应结合 key(是否非 NULL)、rows(预估扫描行数是否合理)、以及 Extra 中是否出现 Using index(覆盖索引)或 Using where(索引条件下推)。









