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是会选择走索引的。

相关推荐
wdfk_prog4 小时前
闹钟定时器(Alarm Timer)初始化:构建可挂起的定时器基础框架
java·linux·数据库
许长安4 小时前
Redis(二)——Redis协议与异步方式
数据库·redis·junit
java_python源码5 小时前
python高校心理健康服务小程序(源码+文档+调试+基础修改+答疑)
数据库·sqlite
简色5 小时前
题库批量(文件)导入的全链路优化实践
java·数据库·mysql·mybatis·java-rabbitmq
CS Beginner6 小时前
【Linux】Mysql的基本文件组成和配置
linux·运维·mysql
点灯小铭6 小时前
基于单片机的自动存包柜设计
数据库·单片机·mongodb·毕业设计·课程设计
失散136 小时前
软件设计师——09 数据库技术基础
数据库·软考·软件设计师
养生技术人7 小时前
Oracle OCP认证考试题目详解082系列第53题
数据库·sql·oracle·database·开闭原则·ocp
银帅183350309717 小时前
2018年下半年试题四:论NoSQL数据库技术及其应用
数据库·架构·nosql