mysql执行计划是如何生成的_mysql执行计划解析

2次阅读

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

mysql执行计划是如何生成的_mysql执行计划解析

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 字段值从 constALL 的真实含义差异

type 描述的是 MySQL 如何定位数据行,不是“快慢等级表”,而是「访问方式的本质区别」。关键不在名字,而在它背后触发的 I/O 行为:

  • const:主键/唯一索引等值匹配,且优化器确认最多一行 → 直接定位磁盘页,无需循环
  • ref:非唯一索引等值查找 → 定位索引 B+ 树叶子页后,需遍历可能的多条记录
  • range:索引范围扫描 → B+ 树中做区间遍历,但不回表(若覆盖索引)或需回表(若未覆盖)
  • index:全索引扫描 → 遍历整个索引 B+ 树叶子链表,比 ALL 快仅因不用读聚簇索引数据页
  • ALL:全表扫描 → 逐行读聚簇索引数据页,最耗 I/O;但若 select * 且表很小,可能比多次随机回表更快

注意:type=index 不代表用了索引就一定好——如果索引字段少、回表代价高,而聚簇索引本身很紧凑,ALL 反而更优。

如何让 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 NULLkey_len 只算 a 长度,b 因为 IS NULL 不参与索引查找
  • utf8mb4VARCHAR(10) 最大占 40 字节(10×4),但若实际值全是 ASCIIkey_len 可能只显示 10
  • TINYINT NOT NULL 占 1 字节,但加了 default NULL 后,key_len 多出 1 字节用于存储 NULL 标志位

真正判断索引是否生效,应结合 key(是否非 NULL)、rows(预估扫描行数是否合理)、以及 Extra 中是否出现 Using index(覆盖索引)或 Using where(索引条件下推)。

text=ZqhQzanResources