!= 操作符几乎总是触发全表扫描,因mysql优化器评估后认为回表成本高于全表扫描;需通过改写为in、范围拆分或覆盖索引等方式绕过。

!= 或 几乎总是触发全表扫描,不是因为语法错误,而是 MySQL 优化器主动放弃索引——它算过账,走索引比扫表更贵。
EXPLAIN 看到 type=ALL 就是确诊全表扫描
只要 EXPLAIN 输出里 type 是 ALL,不管有没有建索引、WHERE 写得多规范,MySQL 都明确告诉你:没走索引,整张表扫一遍。这不是警告,是执行计划铁证。
-
key字段为空 ≠ 没建索引,而是优化器评估后认为“用索引要回表太多次,不如直接扫聚簇索引” -
rows接近表总行数 +Extra含Using where,基本可断定是全表扫描 - 别信“我加了索引就该快”,索引存在 ≠ 被用,必须看
EXPLAIN结果
!= 为什么难走索引:B+ 树不支持“排除式”定位
B+ 树天生适合查“等于某值”或“落在某区间”,但 != 的语义是“除某值外所有行”,无法映射成连续的索引范围。优化器会把它拆成两个区间(比如 k != 6 → k 6),再估算扫描行数和回表成本。
- 如果匹配行数占全表比例高(比如状态字段只有 3 个值,
status != 'paid'返回 70% 行),优化器判定回表开销远超全表扫描,直接选ALL - 即使字段选择性高(如主键),
SELECT *也会让优化器放弃索引——因为二级索引不包含所有列,回表成本爆炸 - 真正走索引的例外极少:仅当被排除值占比极大(比如 95% 的数据都是
k = 6),且查询列能被覆盖索引满足时,才可能触发索引扫描
怎么写才能绕过 != 的全表扫描陷阱
核心思路是把“排除”转为“明确包含”,让优化器能用 range 或 ref 类型走索引。
- 字段取值有限(如状态码、类型枚举):改
WHERE status != 'draft'为WHERE status IN ('published', 'archived') - 数字/时间字段可拆范围:
WHERE age != 25→WHERE age 25(注意:仅当字段有索引且分布偏斜时才有效) - 强制覆盖扫描:只查索引里有的列,比如
SELECT id, status FROM orders WHERE status != 'canceled',并确保有联合索引INDEX idx_status_id (status, id) - 避免
NOT IN含NULL:WHERE id NOT IN (1, 2, NULL)会恒为FALSE,且大概率全表扫;优先用NOT EXISTS
最常被忽略的一点:哪怕你把 != 改成了 IN,如果 SELECT * 还在,优化器仍可能因回表成本高而弃用索引。先想清楚到底要哪些字段,再建对应覆盖索引,否则所有改写都白搭。










