MySQL 索引失效场景分析之NOT IN、!=、IS NOT NULL 何时生效与失效

 更新时间:2026年06月10日 10:03:48   作者:更容易记住我  
在MySQL中,索引的有效性取决于查询的具体写法以及MySQL的查询优化器的行为,特别是在使用NOT IN、!=(或<>)以及IS NOT NULL等操作符时,索引的使用情况可能会有所不同,下面详细解释这些操作符何时可能导致索引失效,以及何时它们可以帮助优化查询性能

一、核心结论(背下来就能用)

  • 单列索引 + NOT IN / != / IS NOT NULL → 99% 索引失效
    • MySQL 优化器会直接判定:全表扫描更快。
  • 联合索引(最左前缀)
    • 前面是等值匹配(=),后面跟 NOT IN / != / IS NOT NULL前面能命中索引,后面失效
    • 也就是说:索引只能用到前面的等值字段,后面的范围 / 否定条件用不上索引。
  • 唯一索引 + 否定条件
    • 依然无法高效使用索引。

二、分场景详细讲(一看就懂)

场景 1:单列索引(最常见)

表结构:

CREATE TABLE user (
    id INT PRIMARY KEY,
    age INT,
    name VARCHAR(50),
    INDEX idx_age(age)
);

这些 SQL 索引一定失效:

SELECT * FROM user WHERE age != 18;
SELECT * FROM user WHERE age NOT IN (18, 19, 20);
SELECT * FROM user WHERE age IS NOT NULL;

原因:
MySQL 优化器认为:否定条件意味着要查询表中的大部分数据(高选择性差),直接进行全表扫描比走索引回表再过滤更快。

场景 2:联合索引(最容易踩坑)

表结构:

CREATE TABLE user (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT,
    INDEX idx_name_age(name, age) -- 联合索引
);

SQL:

SELECT * FROM user
WHERE name = '张三'
  AND age != 18;

结果:
索引只用到 name(等值匹配),age 用不上!
执行过程:

  1. 先通过 name = '张三' 在索引 idx_name_age 中快速定位到所有相关记录。
  2. 然后在得到的结果集里,逐行判断 age != 18(无法再利用索引进行高效过滤)。

场景 3:什么时候 NOT IN / != 能用到索引?(极少)

只有一种情况:否定条件过滤后,剩下的数据极少极少

示例:
假设 status 字段只有 0 和 1,且表中 99% 的数据是 status=1

SELECT * FROM order WHERE status != 1;

这种情况下,MySQL 可能会走索引(因为需要扫描的数据量很小)。但这种数据分布在业务中几乎遇不到!

三、最关键:IS NOT NULL 什么时候失效?

  • 只要字段允许 NULL,IS NOT NULL 一定无法有效使用索引。
  • WHERE email IS NOT NULL
  • MySQL 认为:几乎所有行都满足 IS NOT NULL → 全表扫描。
  • 唯一例外: 字段绝大部分都是 NULL,你查 IS NOT NULL 才会走索引。(现实中极少)

四、总结:你只要记住这 3 条铁律

  1. 单列索引 + NOT IN / != / IS NOT NULL → 索引必失效
  2. 联合索引 + 前面等值,后面否定 → 只命中前面等值部分。
  3. 否定条件天生不适合索引(MySQL 优化器不喜欢)。

五、实战优化方案(遇到 NOT IN 怎么改?)

1. 尽量改成范围查询> < >= <=

-- 原语句(索引失效)
SELECT * FROM user WHERE age NOT IN (18);
-- 优化后(可能走 range 索引扫描)
SELECT * FROM user WHERE age > 18 OR age < 18;
-- 注意:OR 条件可能导致索引失效,需结合实际情况看执行计划

2. 尽量使用覆盖索引

-- 原语句(全表扫描)
SELECT * FROM user WHERE age != 18;
-- 优化后(可能走 index 索引扫描,比全表快)
SELECT age FROM user WHERE age != 18;
-- 因为 age 字段就在索引 idx_age 中,无需回表

3. 业务上避免使用 NOT IN

LEFT JOIN / NOT EXISTS 代替:

-- 查找没有订单的用户
SELECT u.* FROM user u
LEFT JOIN order o ON u.id = o.user_id
WHERE o.id IS NULL;
-- 或
SELECT * FROM user u
WHERE NOT EXISTS (SELECT 1 FROM order o WHERE o.user_id = u.id);

用状态字段代替(0/1): 设计表时,对于是否、开关等字段,尽量使用 TINYINT 表示状态,查询时使用 status = 0 而非 status != 1

六、OR 连接无索引字段:索引失效的隐形杀手

核心定义(一句话说清)

OR 连接无索引字段 = 一边有索引,一边没索引 → 整条 SQL 索引直接失效!

MySQL 的规则很简单:只要 OR 两边任意一个字段没有索引,整个 WHERE 条件就无法使用索引,直接全表扫描。

举个超级简单的例子(一看就明白)

假设你有一张用户表:

CREATE TABLE user (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    phone VARCHAR(20)
);

你只给 name 建了索引,phone 没建索引:

CREATE INDEX idx_name ON user(name);  -- name 有索引
-- phone 无索引

现在你写了这条 SQL:

SELECT * FROM user 
WHERE name = '张三' 
   OR phone = '13800138000';

结果:索引完全失效!
明明 name 有索引,但因为 OR 后面的 phone 没有索引,MySQL 只能:
全表扫描每一行,判断 name 或 phone 是否满足条件

为什么 OR 连无索引字段会失效?(超简单原理)

  • AND 的逻辑:先查有索引的字段 → 缩小范围 → 再查无索引字段(没问题)
  • OR 的逻辑:两个条件任意一个满足就算 → 你必须把整张表都扫一遍,才能确保不漏数据 → 所以索引直接废掉

3 种必失效场景(你写 SQL 时对照看)

场景 1:一边有索引,一边无索引(最常见)

WHERE 有索引字段 = '值' OR 无索引字段 = '值'
-- 结果:索引失效

场景 2:两边都无索引

WHERE 无索引字段1 = '值' OR 无索引字段2 = '值'
-- 结果:本来就没索引,全表扫描

场景 3:联合索引不满足 OR 条件

CREATE INDEX idx_a_b ON table(a, b);  -- 联合索引
WHERE a = 1 OR c = 2
-- 结果:c 无索引 → 失效

什么时候 OR 不会失效?(唯一正确用法)

OR 两边的字段 都 有 独 立 索 引!

例子:

CREATE INDEX idx_name ON user(name);
CREATE INDEX idx_phone ON user(phone);  -- 两个字段都有独立索引
SELECT * FROM user 
WHERE name = '张三' 
   OR phone = '13800138000';

这种情况 MySQL 会使用:index_merge(索引合并)
两边分别走索引,再合并结果,不会失效。

终极总结(背会这 2 句就够了)

  1. OR 只要连接一个无索引字段 → 整条 SQL 索引直接失效,全表扫描!
  2. OR 想要生效 → 两边字段必须都有独立索引。

你以后写 SQL 记住这个口诀:

AND 可以混,OR 必须全!

  • AND:一个有索引就行
  • OR:必须全部有索引,否则完蛋

六、一句话终极总结

只要 SQL 中出现 NOT IN!=IS NOT NULL 这类否定条件,就默认索引不会生效,除非数据分布极端特殊(否定后数据量极少)。在设计和优化时,应尽量避免直接使用这类条件。

到此这篇关于MySQL 索引失效场景分析之NOT IN、!=、IS NOT NULL 何时生效与失效的文章就介绍到这了,更多相关mysql索引失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Centos7 修改mysql指定用户的密码

    详解Centos7 修改mysql指定用户的密码

    本篇文章主要介绍了Centos7 修改mysql指定用户的密码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧。
    2016-12-12
  • MySQL字段类型全面解读

    MySQL字段类型全面解读

    这篇文章主要介绍了MySQL字段类型,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • navicat无法远程连接mysql的解决方法

    navicat无法远程连接mysql的解决方法

    这篇文章主要介绍了navicat无法远程连接mysql的解决方法,需要的朋友可以参考下
    2014-04-04
  • 如何实现mysql的远程连接

    如何实现mysql的远程连接

    这篇文章详细介绍了mysql如何实现远程连接,文中有详细的代码实例讲解,有一定的参考价值,需要的朋友可以参考阅读
    2023-04-04
  • MySQL中数据视图操作详解

    MySQL中数据视图操作详解

    视图是从一个或多个表(或视图)导出的表。视图一经定义,就可以像表一样被查询、修改、删除和更新。本文就来和大家讲讲MySQL数据视图的一些基本操作,需要的可以参考一下
    2022-08-08
  • Mysql数据库分库和分表方式(常用)

    Mysql数据库分库和分表方式(常用)

    本文主要给大家介绍Mysql数据库分库和分表方式(常用),涉及到mysql数据库相关知识,对mysql数据库分库分表相关知识感兴趣的朋友一起学习吧
    2016-03-03
  • MySQL对小数进行四舍五入的操作实现

    MySQL对小数进行四舍五入的操作实现

    数学函数是MySQL中常用的一类函数,其主要用于处理数字,包括整型和浮点数等等,本文主要介绍了MySQL对小数进行四舍五入的操作实现,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • MySQL多表联合查询、连接查询、子查询的实现

    MySQL多表联合查询、连接查询、子查询的实现

    本文主要介绍了MySQL多表联合查询、连接查询、子查询的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • navicat中创建存储过程、触发器和使用游标的简单实例(图文)

    navicat中创建存储过程、触发器和使用游标的简单实例(图文)

    这篇文章主要介绍了navicat中创建存储过程、触发器和使用游标的简单实例,需要的朋友可以参考下
    2017-02-02
  • MySQL中超级有用的14个小知识总结

    MySQL中超级有用的14个小知识总结

    在写SQL时经常灵活运用一些SQL语句编写的技巧,可以大大简化程序逻辑,下面这篇文章主要给大家介绍了关于MySQL中超级有用的14个小知识,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12

最新评论