

最近写了一系列关于MySQL索引相关的文章,帮大家系统全面地把索引这块的知识丰富串联起来,需要回顾或学习这方面的知识的朋友可以看看前面的文章。
今天这篇文章给大家分析和示例一下,MySQL中,当查询条件为IS NULL或 IS NOT NULL时,哪些情况会走索引,哪些情况下又不会走索引。最终结论可能与大家的直觉有所不同。
这篇文章会涉及到前面讲到的B+树相关的知识,请参考《MySQL之进阶:一篇文章搞懂MySQL索引之B+树》一文。
下面我们直接通过具体的实例来看看当查询条件为IS NULL或 IS NOT NULL时,索引的使用情况。
这里采用的MySQL数据库版本为8.0.18,后续实例均采用此版本。
创建一个test表,创建语句如下:
CREATE TABLEtest (
idINT PRIMARY KEY,
col1 INT,
col2 INT,
INDEX idx_col1 (col1)
);
-- 添加两条数据
insertintotestvalues(1,null,1);
insertintotestvalues(2,null,2);
其中在col1列上创建了索引。
此时,我们来看IS NULL和 IS NOT NULL是否走索引。
mysql> explain SELECT * FROMtestWHERE col1 ISNULL \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: Usingindex condition
通过上面的EXPLAIN语句,我们可以看到,当查询条件为IS NULL,且对应查询条件字段上有索引时,MySQL使用索引来处理IS NULL查询条件。
再来看IS NOT NULL查询条件:
mysql> explain SELECT * FROMtestWHERE col1 ISNOTNULL \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: Usingindex condition
当查询条件为IS NOT NULL时,同样使用了索引。
在上面的示例中,我们可以看到无论是IS NULL或 IS NOT NULL都使用索引。
该实例依旧采用上述表结构,初始化3万数据,其中col1中的NULL值约占5%。
mysql> select count(*) from test;
+----------+
| count(*) |
+----------+
| 30000 |
+----------+
在上述情况下,我们再来看看两个查询语句的索引使用情况。
IS NULL查询条件:
mysql> explain SELECT * FROMtestWHERE col1 ISNULL \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: Usingindex condition
可以看到,IS NULL查询条件使用了索引。
IS NOT NULL查询条件:
mysql> explain SELECT * FROMtestWHERE col1 ISNOTNULL \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: Usingwhere
此时,IS NOT NULL查询条件在执行的过程中并没有使用索引,而是采用了全表扫描。
这是因为,当表中的大部分数据都满足 col1 IS NOT NULL 的条件(例如超过一半以上的记录符合条件,本实例中为95%),MySQL 的查询优化器可能会认为使用索引的代价高于全表扫描的代价,从而选择全表扫描。
该实例依旧采用上述表结构,初始化3万数据,其中col1中的NULL值约占95%。
在上述情况下,我们再来看看两个查询语句的索引使用情况。
IS NULL查询条件:
mysql> explain SELECT * FROMtestWHERE col1 ISNULL \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: Usingindex condition
可以看到,IS NULL查询条件使用了索引。
IS NOT NULL查询条件:
mysql> explain SELECT * FROMtestWHERE col1 ISNOTNULL \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: Usingindex condition
可以看到,IS NOT NULL查询条件也使用了索引。
通过上面的三个实例,我们可以看到,无论是IS NULL或IS NOT NULL都是有可能使用索引的。这也证明了网络上一概而论并不正确。
下面,我们就具体分析一下IS NULL和IS NOT NULL是否走索引的核心决定性因素。
在分析MySQL是否使用索引的原因之前,我们先要了解一下针对NULL值,在索引中是如何存储的。
在MySQL的InnoDB引擎中,聚簇索引一般是以主键作为存储依据,主键列的值不能为NULL。所以,针对这种情况,不存在NULL值存储的问题。
对于非聚簇索引中的NULL值,在大多数数据库实现中,NULL值在索引结构中有以下特点:
关于聚簇索引和非聚簇索引的相关知识,可以参考《学习MySQL绕不开的两个基础概念:聚集索引与非聚集索引》一文。
通过上面关于NULL值索引的存储,我们可以看到,在MySQL的InnoDB数据引擎中,NULL也是“有序”的,也可以通过索引(从最左侧开始)进行查找的。大多数关系型数据库也都支持对 NULL 值进行索引。
而真正决定IS NULL或 IS NOT NULL是否走索引的前提是索引能够显著降低执行成本。在MySQL中,查询优化器会根据执行成本决定是否使用索引,而不是单纯地因为 NULL 或 IS NULL 的条件导致索引失效。
例如:
NULL),那么索引命中的数据需要大量回表才能获取完整的记录,这种情况下,优化器可能会选择全表扫描,因为全表扫描的代价会更低。关于回表的内容可参考《MySQL中,什么是回表查询,如何避免和优化?》一文。
因此,关于NULL值索引失效,有以下相关结论:
IS NULL 和 IS NOT NULL 本身不会直接导致索引失效,优化器会根据数据量和数据分布动态选择索引或全表扫描。通过上面的示例我们可以看到,在MySQL中,查询条件使用IS NULL或 IS NOT NULL,理论上都会走索引。部分不走索引的情况也是因为优化器判断全表扫描的效率要高于使用索引,才导致放弃使用索引,而这与查询条件为IS NULL或 IS NOT NULL本身没有直接关系,只和执行成本有关。
所以,针对IS NULL和 IS NOT NULL是否走索引,不能一概而论,还是要回归到数据构成本身,当然,原则上MySQL是会选择走索引的。
全文完,感谢阅读,如果喜欢请三连。