!= 运算符使 B+Tree 索引失效,因其破坏有序性与确定搜索方向,无法界定连续区间,迫使优化器放弃二分查找而退化为全表或范围扫描。

!= 运算符为什么让 B+Tree 索引失效
B+Tree 的查找依赖「有序性」和「确定的搜索方向」。!= 无法给出一个连续的、可跳过的大段数据区间,导致优化器放弃走索引的二分路径,转而回退到全扫描或范围扫描的低效方式。
根本原因不是语法错误,而是语义上破坏了 B+Tree 的结构优势:它没法像 = 或 那样快速定位起点 + 终点,也没法像 <code>BETWEEN 那样划定一个封闭区间。MySQL 查询优化器一看,这条路走不通,干脆不用索引了。
-
!=的结果集在 B+Tree 中天然离散——比如id != 5,意味着要取除第 5 条外的所有叶子节点,无法用一次二分定位+链表遍历完成 - 即使加了索引,
EXPLAIN中type字段大概率显示ALL或index,而不是range或ref - 某些版本(如 MySQL 8.0.17+)对
!=在主键或唯一索引上有有限优化,但仅限于极简场景,不可依赖
OR 连接多个条件时 B+Tree 查找为何中断
当 WHERE 子句中出现 OR,尤其是跨不同索引列或混合索引/非索引列时,B+Tree 的单路径查找逻辑就断了。优化器无法把多个分支条件统一映射到一棵树的一条搜索路径上。
例如 WHERE a = 1 OR b = 2,如果只有 a 有索引、b 没索引,或者 a 和 b 是两个独立的二级索引,MySQL 就不能靠一次 B+Tree 遍历拿到全部结果——它得分别查两次,再合并结果集,这个过程不走联合索引的二分逻辑。
- 只有当所有
OR分支都命中「同一个复合索引」的最左前缀,且能推导出连续区间时,才可能走索引(例如WHERE (a,b) = (1,2) OR (a,b) = (1,3)) - 更常见的是被重写为
UNION,这时每个子查询可以单独走索引,但代价是多一次执行计划解析和结果合并 -
OR+NULL判断(如a = 1 OR a IS NULL)几乎必然导致索引失效,因为 NULL 不参与 B+Tree 排序比较
哪些等价写法能绕过 != 和 OR 的索引陷阱
不是所有 != 或 OR 都必须硬扛全表扫描。关键看能不能把语义转换成 B+Tree 友好的区间操作或覆盖路径。
- 用
NOT IN替代!=并不解决问题,反而更糟(含NULL时结果不可控,且同样无法利用索引) - 把
WHERE status != 'done'改成WHERE status IN ('pending', 'processing', 'failed'),前提是枚举值稳定且数量可控——这样每个值都能走等值查找 - 对
OR,优先考虑改写为UNION ALL,并确保每个分支都有对应索引支撑,例如:SELECT * FROM t WHERE a = 1 UNION ALL SELECT * FROM t WHERE b = 2 AND a IS NULL;
- 复合索引设计上预留组合空间,比如把常一起
OR的字段建为(a,b),再配合IN或范围条件使用
真正容易被忽略的底层细节
很多人以为只要“建了索引”,查询就一定走 B+Tree 二分;实际上,哪怕语句只差一个符号,整个执行路径就可能从 O(log n) 退化成 O(n)。
尤其要注意:B+Tree 的「二分查找」只发生在单次等值或范围边界定位阶段;一旦涉及否定、离散、多路径,它就不再是一个查找动作,而变成多次查找 + 合并 + 过滤的组合操作——这时候磁盘 IO 次数、临时表、排序缓冲区压力全都变了。
线上慢查询里,!= 和 OR 相关的 type: ALL 出现场景远比想象中高频,但往往被 EXPLAIN 输出里的 key: xxx 假象迷惑,以为索引还在生效。


















