key为null表明mysql未使用索引,主因是优化器基于成本模型判定全表扫描更优,或预处理器改写条件导致索引无法映射到b+树查找路径。

EXPLAIN 显示 key 为 NULL,或者 type 是 ALL,基本就能断定索引没走——但这不是 MySQL “故意不走”,而是预处理器和优化器在各自环节筛掉了索引路径。
EXPLAIN 里 key 为 NULL 的真实原因
MySQL 查询执行分两步:预处理(语法/语义检查)→ 优化(生成执行计划)。索引是否参与,取决于优化器是否认为“走索引比全表扫描更优”。
- 预处理器只管合法性:字段是否存在、函数名拼写对不对、类型能不能隐式转换。它不决定是否用索引,但会悄悄改写条件(比如把
id='123'转成id=123),这一步就埋下失效隐患。 - 优化器基于成本模型估算:它看
WHERE条件能否利用索引的有序性,再对比 IO 次数、内存开销。一旦发现“查 80% 的行”,哪怕有索引,也会直接选ALL。
常见触发点:
-
WHERE中对索引列用了DATE()、UPPER()、col + 1等表达式 → 预处理器无法将条件映射到索引 B+ 树的有序键上 - 联合索引
(a,b,c),只查WHERE b = 1→ 优化器发现跳过最左列a后,无法定位起始页,放弃索引 -
WHERE a != 1且没覆盖索引 → 优化器估算回表代价太高,不如扫全表
WHERE 条件被预处理器“改写”后为何索引失效
预处理器会在执行前做隐式类型转换或常量折叠,但这些操作让原始索引值失真。
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
- 字段是
VARCHAR,查询写成WHERE id = 123→ 预处理器自动转成字符串比较,但索引 B+ 树里存的是字节序,而数字转字符串的排序规则(如 padding、collation)和原索引不一致,导致范围无法界定 - 字段是
DATETIME,写成WHERE create_time > '2024-04-01'→ 看似没问题,但如果字段实际带时区或精度(如create_time(6)),而字符串字面量没对齐,预处理器可能截断或补零,破坏索引有序性 - 使用参数化查询时传入
null或空字符串 → 预处理器保留IS NULL或= '',但IS NOT NULL在非首列联合索引中直接让整条索引失效
关键判断:只要预处理器输出的最终条件表达式里,索引列不再以“裸值”形式出现(即不再是 col = ?、col > ? 这类可直接映射到 B+ 树查找路径的形式),优化器大概率放弃该索引。
为什么 OR 条件经常导致 key 变 NULL
OR 不是简单地“多个条件并列”,而是触发优化器的分支评估逻辑。
- 如果
OR两边都命中同一索引的前缀列(如索引(a,b),条件是WHERE a = 1 OR a = 2),优化器可能合并为range扫描 - 但只要有一边没索引(如
WHERE a = 1 OR c = 1,c无索引),优化器必须保证结果完整性,只能退化为全表扫描 —— 因为没法用一个索引覆盖两个不同结构的查找路径 - 更隐蔽的是:即使两边都有索引,但属于不同索引(如
a有单列索引,c也有单列索引),MySQL 5.7+ 默认不使用 index merge,除非显式开启optimizer_switch='index_merge=on'
所以看到 OR 就该本能检查:所有 OR 分支涉及的列,是否共用同一个索引的连续前缀?如果不是,key 基本就是 NULL。
真正卡住人的地方,往往不在“有没有建索引”,而在“优化器根本没把它当候选”。EXPLAIN 的 key 和 key_len 是唯一可信信号;而预处理器那层看不见的改写,才是多数人调试时漏掉的第一环。










