!=几乎不走索引,因其语义为离散排除而非连续定位,优化器评估回表I/O成本高于全表扫描;仅在值高度倾斜、覆盖索引或MySQL 8.0+隐式优化等极少数场景可能例外。

!= 为什么几乎不走索引
B+ 树索引不是万能的,它擅长快速定位「等于某值」或「落在某连续区间」的数据,但 != 表达的是“除这个值以外的所有行”——物理上完全离散、无法用单一起始+终止位置描述。优化器一看:走索引得先查出所有匹配页,再逐个回表取数据,I/O 成本可能比直接扫一遍聚簇索引还高,干脆选全表扫描。
EXPLAIN 看到 type=ALL 就是没走索引
执行 EXPLAIN SELECT * FROM users WHERE age != 25;,如果 key 列为 NULL、type 是 ALL 或 index,且 Extra 出现 Using where,基本可确认走了全表扫描。这不是 bug,是优化器基于统计信息(如值分布、表大小、索引选择性)主动放弃索引的结果。
哪些情况可能例外地走索引
极少数场景下 != 可能触发索引扫描,但不可依赖:
- 当索引列值高度倾斜(比如 99.9% 都是 25,只有 0.1% 是其他值),优化器估算走索引回表代价更低
- 使用覆盖索引(
SELECT id FROM users WHERE age != 25;,且age和id在联合索引中),避免回表时 I/O 压力骤降 - MySQL 8.0+ 对某些简单范围改写做了隐式优化,但行为不稳定,不能作为设计依据
更靠谱的替代写法
想让查询真正利用索引,得把语义从「排除」转成「定位」:
- 改写为范围组合:
WHERE age 25—— 这两个条件各自可走 B+ 树范围扫描 - 若离散值有限(比如状态码只有 0/1/2/3),用
IN:WHERE status IN (1, 2, 3) - 涉及子查询排除时,优先用
NOT EXISTS而非NOT IN,后者遇到 NULL 会整个条件失效 - 确认字段无 NULL 时,可加
AND age IS NOT NULL辅助优化器判断,但注意IS NOT NULL本身也不保证走索引
实际效果取决于数据分布和 MySQL 版本,别只看语句长得像索引友好,EXPLAIN 才是唯一可信依据。


















