MySQL中,IS NULL和IS NOT NULL不会走索引?错!

最近写了一系列关于MySQL索引相关的文章,帮大家系统全面地把索引这块的知识丰富串联起来,需要回顾或学习这方面的知识的朋友可以看看前面的文章。

今天这篇文章给大家分析和示例一下,MySQL中,当查询条件为IS NULLIS NOT NULL时,哪些情况会走索引,哪些情况下又不会走索引。最终结论可能与大家的直觉有所不同。

这篇文章会涉及到前面讲到的B+树相关的知识,请参考《MySQL之进阶:一篇文章搞懂MySQL索引之B+树》一文。

下面我们直接通过具体的实例来看看当查询条件为IS NULLIS NOT NULL时,索引的使用情况。

实例一:少量数据,使用索引

这里采用的MySQL数据库版本为8.0.18,后续实例均采用此版本。

创建一个test表,创建语句如下:

sql 复制代码
CREATE TABLE test (
    id INT PRIMARY KEY,
    col1 INT,
    col2 INT,
    INDEX idx_col1 (col1)
);
​
-- 添加两条数据
insert into test values(1,null,1);
insert into test values(2,null,2);

其中在col1列上创建了索引。

实例演示

此时,我们来看IS NULLIS NOT NULL是否走索引。

yaml 复制代码
mysql> explain SELECT * FROM test WHERE col1 IS NULL \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: ref
possible_keys: idx_col1
          key: idx_col1
      key_len: 5
          ref: const
         rows: 1
     filtered: 100.00
        Extra: Using index condition

通过上面的EXPLAIN语句,我们可以看到,当查询条件为IS NULL,且对应查询条件字段上有索引时,MySQL使用索引来处理IS NULL查询条件。

再来看IS NOT NULL查询条件:

yaml 复制代码
mysql> explain SELECT * FROM test WHERE col1 IS NOT  NULL \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: range
possible_keys: idx_col1
          key: idx_col1
      key_len: 5
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using index condition

当查询条件为IS NOT NULL时,同样使用了索引。

在上面的示例中,我们可以看到无论是IS NULLIS NOT NULL都使用索引。

实例二:大量数据,少量NULL值

该实例依旧采用上述表结构,初始化3万数据,其中col1中的NULL值约占5%。

sql 复制代码
mysql> select count(*) from test;
+----------+
| count(*) |
+----------+
|    30000 |
+----------+

在上述情况下,我们再来看看两个查询语句的索引使用情况。

IS NULL查询条件:

yaml 复制代码
mysql> explain SELECT * FROM test WHERE col1 IS NULL \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: ref
possible_keys: idx_col1
          key: idx_col1
      key_len: 5
          ref: const
         rows: 1551
     filtered: 100.00
        Extra: Using index condition

可以看到,IS NULL查询条件使用了索引。

IS NOT NULL查询条件:

yaml 复制代码
mysql> explain SELECT * FROM test WHERE col1 IS NOT  NULL \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: ALL
possible_keys: idx_col1
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 30570
     filtered: 50.00
        Extra: Using where

此时,IS NOT NULL查询条件在执行的过程中并没有使用索引,而是采用了全表扫描。

这是因为,当表中的大部分数据都满足 col1 IS NOT NULL 的条件(例如超过一半以上的记录符合条件,本实例中为95%),MySQL 的查询优化器可能会认为使用索引的代价高于全表扫描的代价,从而选择全表扫描。

实例三:大量数据,大量NULL值

该实例依旧采用上述表结构,初始化3万数据,其中col1中的NULL值约占95%。

在上述情况下,我们再来看看两个查询语句的索引使用情况。

IS NULL查询条件:

yaml 复制代码
mysql> explain SELECT * FROM test WHERE col1 IS NULL \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: ref
possible_keys: idx_col1
          key: idx_col1
      key_len: 5
          ref: const
         rows: 14957
     filtered: 100.00
        Extra: Using index condition

可以看到,IS NULL查询条件使用了索引。

IS NOT NULL查询条件:

yaml 复制代码
mysql> explain SELECT * FROM test WHERE col1 IS NOT  NULL \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: range
possible_keys: idx_col1
          key: idx_col1
      key_len: 5
          ref: NULL
         rows: 1558
     filtered: 100.00
        Extra: Using index condition

可以看到,IS NOT NULL查询条件也使用了索引。

通过上面的三个实例,我们可以看到,无论是IS NULLIS NOT NULL都是有可能使用索引的。这也证明了网络上一概而论并不正确。

下面,我们就具体分析一下IS NULLIS NOT NULL是否走索引的核心决定性因素。

索引是如何存储NULL值的?

在分析MySQL是否使用索引的原因之前,我们先要了解一下针对NULL值,在索引中是如何存储的。

在MySQL的InnoDB引擎中,聚簇索引一般是以主键作为存储依据,主键列的值不能为NULL。所以,针对这种情况,不存在NULL值存储的问题。

对于非聚簇索引中的NULL值,在大多数数据库实现中,NULL值在索引结构中有以下特点:

  • B+树的排序:NULL值通常被看作是最小值,因此索引存储中,NULL会排在树的最左边。
  • 链表形式存储:B+树叶子节点存储数据的引用,而叶子节点之间是顺序链接的,也就是说,包含NULL的记录会集中在最左侧,沿着链表可以顺序读取这些记录。
  • 查找NULL值:当查询列值为NULL时,数据库的索引机制可以通过扫描树的最左侧开始查找所有NULL值。

关于聚簇索引和非聚簇索引的相关知识,可以参考《学习MySQL绕不开的两个基础概念:聚集索引与非聚集索引》一文。

索引失效的原因

通过上面关于NULL值索引的存储,我们可以看到,在MySQL的InnoDB数据引擎中,NULL也是"有序"的,也可以通过索引(从最左侧开始)进行查找的。大多数关系型数据库也都支持对 NULL 值进行索引。

而真正决定IS NULLIS NOT NULL是否走索引的前提是索引能够显著降低执行成本。在MySQL中,查询优化器会根据执行成本决定是否使用索引,而不是单纯地因为 NULLIS NULL 的条件导致索引失效。

例如:

  • 如果通过索引查询的结果集非常大(例如大多数记录都为 NULL),那么索引命中的数据需要大量回表才能获取完整的记录,这种情况下,优化器可能会选择全表扫描,因为全表扫描的代价会更低。
  • 如果通过索引可以显著减少数据访问和回表的次数(例如查询结果集很小或者覆盖索引被使用),MySQL一般会选择使用索引。

关于回表的内容可参考《MySQL中,什么是回表查询,如何避免和优化?》一文。

因此,关于NULL值索引失效,有以下相关结论:

  • NULL 值可以走索引,但是否使用索引取决于具体的执行成本。
  • IS NULLIS NOT NULL 本身不会直接导致索引失效,优化器会根据数据量和数据分布动态选择索引或全表扫描。
  • 非聚簇索引通常需要回表,回表成本过高时,优化器可能放弃索引。
  • 查询性能优化需要结合具体的场景和数据分布分析,而不能简单地说某些条件下索引会失效。

小结

通过上面的示例我们可以看到,在MySQL中,查询条件使用IS NULLIS NOT NULL,理论上都会走索引。部分不走索引的情况也是因为优化器判断全表扫描的效率要高于使用索引,才导致放弃使用索引,而这与查询条件为IS NULLIS NOT NULL本身没有直接关系,只和执行成本有关。

所以,针对IS NULLIS NOT NULL是否走索引,不能一概而论,还是要回归到数据构成本身,当然,原则上MySQL是会选择走索引的。

相关推荐
档案宝档案管理1 小时前
档案宝:企业合同档案管理的“安全保险箱”与“效率加速器”
大数据·数据库·人工智能·安全·档案·档案管理
wangjialelele1 小时前
mysql库操作二
数据库·mysql
星释1 小时前
二级等保实战:MySQL安全加固
android·mysql·安全
Chloeis Syntax1 小时前
MySQL初阶学习日记(1)--- 数据库的基本操作
数据库·学习·mysql
workflower2 小时前
FDD(Feature Driven Development)特征驱动开发
大数据·数据库·驱动开发·需求分析·个人开发
韩立学长3 小时前
基于Springboot的旧物公益捐赠管理系统3726v22v(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
苦学编程的谢3 小时前
Redis_4_常见命令(完)+认识数据类型和编码方式
数据库·redis·缓存
小光学长3 小时前
基于Vue的儿童手工创意店管理系统as8celp7(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
前端·数据库·vue.js
小光学长4 小时前
基于Vue的地铁综合服务管理系统7949eg04(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
前端·数据库·vue.js