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

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

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

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

下面我们直接通过具体的实例来看看当查询条件为IS NULL或 IS 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 NULL和 IS 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 NULL或 IS 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 NULL或IS NOT NULL都是有可能使用索引的。这也证明了网络上一概而论并不正确。

下面,我们就具体分析一下IS NULL和IS 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 NULL或 IS NOT NULL是否走索引的前提是索引能够显著降低执行成本。在MySQL中,查询优化器会根据执行成本决定是否使用索引,而不是单纯地因为 NULL 或 IS NULL 的条件导致索引失效。

例如:

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

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

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

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

小结

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

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

相关推荐
库库的数据库笔记7 分钟前
金融核心交易系统的数据库国产信创改造该如何做?
大数据·数据库·分布式·mysql·oracle·金融·数据库架构
Linux实战笔记13 分钟前
MySQL 索引失效的三种典型场景与 EXPLAIN 分析
mysql·索引失效·explain·执行计划·sql 调优
这个DBA有点耶15 分钟前
连接数没满,应用却连不上数据库——TCP backlog溢出与somaxconn的隐藏限制
数据库·mysql·代码规范
yunlaodacom18 分钟前
华为云国际站服务代理商:BMS数据库延迟突然升高?磁盘I/O、网络与CPU排查方法
网络·数据库·华为云
SelectDB技术团队20 分钟前
Apache Doris 的时间旅行与快照能力:边界、替代路径与可落地操作
数据库·apache·数据湖·apache doris·数据库选型·多模湖仓
2601_9619633822 分钟前
电子合同检索与全文搜索怎么测、怎么选:六个指标 + 五家平台对比 + 八个自测问题
数据库·后端·python·django·智能合约
zhangjw341 小时前
1-3 PostgreSQL 架构模型:实例、数据库、Schema 模式、表空间完整概念
数据库·postgresql·架构
小羊没烦恼!1 小时前
C#综合揭秘——Entity Framework 并发处理详解
java·开发语言·前端·javascript·数据库·mysql·c#
Databend1 小时前
周年庆结束了,湖仓为什么还在高消费?
大数据·数据库·agent
笃行3501 小时前
数据集成软件里最贵的一行字,叫“用同步工具做增量同步“
数据库