为什么 `!=` 和 `NOT IN` 会让索引失效:从 B+ 树的有序性说起
前言“这条 SQL 明明在索引列上查怎么还是全表扫描”如果你把WHERE status 1改成WHERE status ! 1很可能就会遇到这个现象同一个列、同一个索引等值查询走得好好的一换成!或NOT IN、索引就失效了。《阿里巴巴 Java 开发手册》里有一条相关的**【推荐】**规约【推荐】SQL 性能优化的目标至少要达到 range 级别要求是 ref 级别如果可以是 consts 最好。说明1consts单表中最多只有一个匹配行主键或者唯一索引在优化阶段即可读取到数据。2ref指的是使用普通的索引normal index。3range对索引进行范围检索。反例explain结果typeindex索引物理文件全扫描速度非常慢。!和NOT IN之所以危险正是因为它们很容易让查询掉到range级别以下退化成全表扫描。这篇文章从 B 树的结构讲清楚为什么不等于这类否定条件天生就和索引不对付。环境说明本文基于 MySQL 8.0存储引擎 InnoDB。继续复用前几篇的orders表100 万行。一、先看现象一个!让索引失效orders表上有联合索引idx_user_status(user_id, status)。先看等值查询EXPLAINSELECT*FROMordersWHEREuser_id88888;--------------------------------------------------------- | id | table | type | key | key_len | rows | Extra | --------------------------------------------------------- | 1 | orders | ref | idx_user_status | 8 | 10 | NULL | ---------------------------------------------------------type ref走索引只扫 10 行。很理想。现在把换成!EXPLAINSELECT*FROMordersWHEREuser_id!88888;-------------------------------------------------------------------------------------- | id | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | -------------------------------------------------------------------------------------- | 1 | orders | ALL | idx_user_status| NULL | NULL | NULL | 1000000 | 99.99 | Using where | --------------------------------------------------------------------------------------type ALL、key NULL——索引没用上直接全表扫 100 万行。possible_keys里明明有idx_user_status说明这个索引可用但优化器最终选择了不用它。NOT IN也是一样EXPLAINSELECT*FROMordersWHEREuser_idNOTIN(88888,99999);-- 同样 type ALL全表扫描为什么会这样答案在索引的底层结构里。二、底层索引为什么怕否定条件2.1 B 树的本质是有序InnoDB 的索引是 B 树它最核心的特性是叶子节点上的数据是按索引列的值从小到大排好序的。正因为有序索引才能高效地做两件事等值查找 88888像查字典一样直接二分定位到那个值O(log n)。范围查找 88888、BETWEEN、 88888定位到范围的起点然后顺着有序的叶子链表往后连续读读到终点为止。关键词是连续。索引能加速靠的就是把要找的数据圈定在一段连续的区间里一次定位、顺序扫描。2.2!圈出来的不是一段区间而是两段 挖空现在看user_id ! 88888要的是什么除了 88888 以外的所有值。在有序的 B 树上这意味着要的是88888左边的一整段 88888加上右边的一整段 88888中间挖掉一个点。这就麻烦了它不是一段连续区间而是两段中间还断开更要命的是这两段加起来几乎是整张表——排除掉一个值剩下的还是绝大多数数据优化器一算走索引的话要扫描几乎全部索引项还得每条回表取完整数据因为是SELECT *这么大的量回表的代价比直接全表顺序扫还高。于是它干脆放弃索引选择全表扫描。这就是!/NOT IN/让索引失效的真相不是不能用而是它们圈定的数据范围太大、太碎优化器算下来用索引反而更慢主动放弃了。2.3 对比和为什么就没事 88888圈定的是一个点命中极少走索引稳赚。 88888圈定的是一段连续区间如果这段不算太大走索引扫这一段仍然比全表扫划算。! 88888圈定的是几乎全表走索引毫无优势反而多了回表开销。看出规律了吗索引怕的不是否定本身而是要的数据范围太大。!恰好几乎总是圈中绝大部分数据所以几乎总是失效。三、这是优化器的选择不是语法禁止有一点要澄清!让索引失效是优化器基于成本的主动选择不是 MySQL 语法上禁止!用索引。证据是如果否定条件排除掉的是大部分数据即最终只剩一小部分优化器又会愿意走索引了。举个例子假设某个status值占了全表 99% 的数据那status ! 那个值只剩 1%这时候走索引扫这 1% 就划算了优化器可能就会用索引。也就是说最终结果集占全表的比例才是优化器决策的关键结果集占比小 → 走索引划算 → 用索引结果集占比大!通常如此→ 走索引还不如全表扫 → 放弃索引所以严格讲不是!一定不走索引而是!通常命中太多行导致优化器算下来不划算。理解这一层比死记!让索引失效更有用。四、那该怎么办4.1 能改成范围/等值就改如果!在业务上可以等价改写成一段明确的范围就改。比如状态不是已完成3如果状态只有 0/1/2/3可以写成IN-- 不推荐WHEREstatus!3-- 如果能明确列举改成 IN正向枚举WHEREstatusIN(0,1,2)IN是正向的、离散的等值集合每个值都能走索引定位比!友好得多。4.2 接受它但别让它扫大表有些!无法避免。那就要保证它不是在大表上裸跑——通过其他更有选择性的条件先把范围缩小。比如-- user_id 先用索引把范围缩到几十行再在这几十行里过滤 status ! 3WHEREuser_id88888ANDstatus!3这条 SQL 里user_id 88888先走索引定位到约 10 行status ! 3只是在这极小的结果集里做过滤完全没问题。让高选择性的等值条件走索引把!降级为过滤而非检索。4.3 用 EXPLAIN 确认别猜最实在的办法写完 SQL 用EXPLAIN看一眼type。对照手册那条规约type ALL→ 全表扫描最差要优化type index→ 全索引扫描也慢type range→ 及格线范围扫描type ref→ 良好普通索引等值type const→ 最优主键/唯一索引只要没掉到range以下就基本达标。五、常见误区与面试高频问答Q所有!都一定不走索引吗不是。这是优化器基于结果集占比的成本选择。当!排除后剩下的数据很少时优化器仍可能走索引。只是大多数场景下!命中绝大部分行所以通常失效。别绝对化。QNOT IN和!是一回事吗原理一样都是否定条件圈定的都是排除某些值后的剩余大部分数据所以都容易失效。另外NOT IN遇到子查询、遇到 NULL 时还有额外的坑NULL 会导致整个结果异常能用NOT EXISTS或正向IN时优先考虑。QIS NOT NULL也会失效吗同理取决于非 NULL 的行占多大比例。如果绝大多数行都非 NULLIS NOT NULL命中几乎全表也会倾向全表扫描。Q为什么possible_keys有索引key却是 NULL这正是索引可用但优化器不用的典型信号。possible_keys表示这个索引理论上能用于这个查询key NULL表示优化器算完成本后决定不用它——通常就是因为走索引的代价大量回表比全表扫还高。总结!/NOT IN/让索引失效根子在 B 树的有序结构索引靠有序加速擅长圈定一段连续区间等值是一个点范围是一段。!圈定的是排除一个值后的几乎全表——不是连续区间且数据量巨大。优化器算下来走索引大量回表还不如直接全表顺序扫于是主动放弃索引type掉到ALL。本质是结果集占比决定的成本选择不是语法禁止。占比小时!也能走索引。应对能正向枚举就用IN避免不了就用高选择性的等值条件先缩小范围让!只做过滤最后用EXPLAIN确认type不低于range。一句话记忆索引怕的不是否定是范围太大。!几乎总是命中绝大部分数据所以几乎总是失效——把它降级成过滤条件别让它当检索条件。

相关新闻

WeChatExporter终极教程:三步永久备份你的微信聊天记录

WeChatExporter终极教程:三步永久备份你的微信聊天记录

WeChatExporter终极教程:三步永久备份你的微信聊天记录 【免费下载链接】WeChatExporter 一个可以快速导出、查看你的微信聊天记录的工具 项目地址: https://gitcode.com/gh_mirrors/wec/WeChatExporter 你是否曾因为手机丢失、系统升级或微信清理而丢失了珍…

2026/7/24 19:10:21阅读更多 →
深入解析TAS3204音频DSP:DAP核心、I2C加载与启动序列实战

深入解析TAS3204音频DSP:DAP核心、I2C加载与启动序列实战

1. 项目概述:深入TAS3204音频DSP的内核与交互在嵌入式音频系统设计里,选对一颗数字信号处理器(DSP)只是第一步,真正考验工程师功力的,是如何“驯服”它。你得理解它的运算核心如何吞吐数据,知道…

2026/7/24 19:10:21阅读更多 →
从0到1上手Hermes:Hermes Agent从安装到模型接入使用(保姆级)

从0到1上手Hermes:Hermes Agent从安装到模型接入使用(保姆级)

前言 最近AI Agent工具更新很快,不少人想试试Hermes这个"会成长的助理",但对于我们最大的卡点是账号和环境问题。这篇文章帮你解决,从下载安装到模型跑通,一步步带你实操,尽量少踩坑,让你快速用…

2026/7/24 19:10:21阅读更多 →
Locale Emulator终极指南:快速解决多语言软件乱码问题

Locale Emulator终极指南:快速解决多语言软件乱码问题

Locale Emulator终极指南:快速解决多语言软件乱码问题 【免费下载链接】Locale-Emulator Yet Another System Region and Language Simulator 项目地址: https://gitcode.com/gh_mirrors/lo/Locale-Emulator 你是否遇到过下载日文游戏或软件时,打…

2026/7/24 20:46:39阅读更多 →
Draw.io(Diagrams)图表绘制工具

Draw.io(Diagrams)图表绘制工具

近期在设计一款工具软件的时候随着代码越写越多,思路越来越乱,代码不仅冗余而且运行效率也不高,漏洞百出,后来发现当考虑处理的情况越多时,流程就会变得越复杂,必须得先画一个流程图来理清思路后才能精简代…

2026/7/24 20:46:39阅读更多 →
QMK Toolbox:彻底改变机械键盘刷写体验的终极解决方案

QMK Toolbox:彻底改变机械键盘刷写体验的终极解决方案

QMK Toolbox:彻底改变机械键盘刷写体验的终极解决方案 【免费下载链接】qmk_toolbox A Toolbox companion for QMK Firmware 项目地址: https://gitcode.com/gh_mirrors/qm/qmk_toolbox 你是否曾因复杂的命令行刷写过程而放弃自定义键盘功能?是否…

2026/7/24 20:46:39阅读更多 →
ts3380,g6080,ip110,g2800,g3800,ip2780,ts3480,ts9020,ts6380故障码5B00,5B02,P07,E08,1700,1702,1704清零即可维修好

ts3380,g6080,ip110,g2800,g3800,ip2780,ts3480,ts9020,ts6380故障码5B00,5B02,P07,E08,1700,1702,1704清零即可维修好

蓝奏云:点这里下载 密码:00 百度云:点这里下载 备用:pan.baidu.com/s/1gls2G4rqWWP-Mw-z6tVjnQ?pwd0000 常见型号如下: G1000、G1100、G1200、G1400、G1500、G1800、G1900、G1010、G1110、G1120、G1410、G1420、G1411、G151…

2026/7/24 20:46:39阅读更多 →
英伟达的quadro k4200在FreeBSD下最新驱动是啥? 可以用51xx的驱动吗?答案是可以,但是FreeBSD15.1的包里面没有

英伟达的quadro k4200在FreeBSD下最新驱动是啥? 可以用51xx的驱动吗?答案是可以,但是FreeBSD15.1的包里面没有

英伟达的quadro k4200在FreeBSD下最新驱动是啥? 可以用51xx的驱动吗?核心结论‌Quadro K4200 可以用 51xx 系列驱动‌,它的最新兼容正式驱动是 ‌NVIDIA 525 分支‌,完全支持该显卡。‌显卡架构确认‌Quadro K4200 属于 ‌Kepler …

2026/7/24 20:46:39阅读更多 →
QML 文字开幕与入场动画:幕布、缩小、旋转

QML 文字开幕与入场动画:幕布、缩小、旋转

目录 Demo 1 文字开幕 演示代码 关键逻辑解析 Demo 2 文字缩小入场 演示代码 关键逻辑解析 Demo 3 文字旋入 演示代码 关键逻辑解析 运行验证 扩展复用方向 工程下载 文字入场是 UI 动效里最常用的一类效果。启动页标题、弹窗提示、页面切换时的强调文字,只要让文字以合适的方…

2026/7/24 20:44:39阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 0:58:53阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 0:58:53阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/24 0:58:53阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:06阅读更多 →
【LeetCode 54】螺旋矩阵

【LeetCode 54】螺旋矩阵

问题描述: 解法: 1、模拟(参考自【LeetCode 54】螺旋矩阵-CSDN博客) int *spiralOrder(int **matrix, int matrixSize, int *matrixColSize, int *returnSize) {static const int dirs[4][2] {{0, 1}, {1, 0}, {0, -1}, {-1, …

2026/7/24 0:00:06阅读更多 →
2026 WAIC:模型隐身、智能体疯野,厂商竞赛聚焦办公场景与商业闭环

2026 WAIC:模型隐身、智能体疯野,厂商竞赛聚焦办公场景与商业闭环

知春路不相信模型领先今年WAIC大会,昔日AI六小龙来了五家,分别是Kimi、阶跃星辰、Minimax、百川智能、零一万物。连放弃基模的百川和零一万物都来了,唯一缺席的竟是近几个月来风光无限的智谱。(DeepSeek一直不参加)WAI…

2026/7/24 0:00:06阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/23 22:58:43阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/24 19:00:40阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/24 19:00:40阅读更多 →