MySQL对!=不走索引,因B+树无法高效定位“非某值”的离散范围,优化器评估后认为全表扫描比回表更省IO;仅当字段取值极少且配合高选择性条件时才可能走索引。

UPDATE 中用 != 或 条件,索引大概率不走 —— 不是语法错,是优化器主动放弃。
MySQL 为什么对 != 条件不走索引?
因为 B+ 树索引本质是有序结构,=、IN、左前缀 LIKE 'abc%' 这类条件能快速定位起始位置并连续读取;而 != 表示“除了某值以外的所有行”,无法利用索引的有序性做高效跳转。优化器评估后常认为:全表扫描 + 判断每行是否满足 !=,比反复回表或范围跳查更省 IO。
注意:这不是绝对规则 —— 如果字段基数极低(比如只有 3 个值),且 != 'X' 匹配了 95% 的数据,优化器反而可能走索引(配合 range 类型);但这种例外极少,不能依赖。
UPDATE ... WHERE col != ? 的实际执行表现
执行 EXPLAIN UPDATE ... WHERE col != 'val'(注意:MySQL 8.0+ 支持对 UPDATE 用 EXPLAIN)会发现 type 是 ALL,key 为 NULL,即没用索引。
- 即使
col上有单列索引或联合索引的最左列,也基本失效 - 如果该字段同时参与了
ORDER BY或LIMIT,依然不改变索引不被选中的事实 - 在大表上执行这类语句,容易触发长事务、锁升级(如从行锁升为表锁),影响并发
替代写法:用范围拆分代替 !=
把 “不等于” 拆成两个可走索引的范围条件,通常能恢复索引使用:
UPDATE users SET status = 2 WHERE age != 25;
→ 改为:
UPDATE users SET status = 2 WHERE age < 25 OR age > 25;
前提是 age 有索引,且类型一致(比如都是 INT)。这样 EXPLAIN 会显示 type=range,key 显示实际使用的索引。
其他常见等价改法:
- 用
NOT IN(仅当右值确定、无 NULL 时安全):WHERE id NOT IN (1, 2, 3) - 用
NOT EXISTS(适合子查询场景) - 业务层先
SELECT id WHERE col = ?拿到排除 ID 列表,再UPDATE ... WHERE id NOT IN (...)(小批量适用)
真正容易被忽略的点
很多人以为加了索引就万事大吉,但 != 是典型的“写了索引也白搭”的操作符。更隐蔽的是:它常和隐式类型转换共存 —— 比如 WHERE status != 1,而 status 是 VARCHAR,此时不仅 != 失效,还会触发类型转换,双重失效。务必用 SHOW CREATE TABLE 确认字段类型,并保持 WHERE 右侧字面量类型严格一致。


















