Code前端首页关于Code前端联系我们

MySQL 的索引中不能使用 IS NULL、IS NOT NULL、!= 吗?

terry 2年前 (2023-09-26) 阅读数 46 #数据库

网上流传着这样一句话:

当MySQL WHERE子句IS NULL、IS NOT NULL、!=这些条件时,不能使用索引查询,只能使用全表扫描。

这个说法越来越流行,甚至很多学生都信以为真。啥也不说,直接举个例子。假设我们有一个表 s1,其结构如下:

CREATE TABLE s1 (
    id INT NOT NULL AUTO_INCREMENT,
    key1 VARCHAR(100),
    key2 VARCHAR(100),
    key3 VARCHAR(100),
    key_part1 VARCHAR(100),
    key_part2 VARCHAR(100),
    key_part3 VARCHAR(100),
    common_field VARCHAR(100),
    PRIMARY KEY (id),
    KEY idx_key1 (key1),
    KEY idx_key2 (key2),
    KEY idx_key3 (key3),
    KEY idx_key_part(key_part1, key_part2, key_part3)
) Engine=InnoDB CHARSET=utf8;
复制代码

此表中有 10,000 条记录:

mysql> SELECT COUNT(*) FROM s1;
+----------+
| COUNT(*) |
+----------+
|    10000 |
+----------+
1 row in set (0.00 sec)
复制代码

让我们在下面放一些查询图片: ❙ry WHERE 子句 是IS NULL IS NOT NULL,但是从他们的执行中可以看出,!计划,这些语句都使用适当的二级索引来执行查询,而不是使用所谓的全表扫描。谣言是自欺欺人的。当然,本文无意驳斥这些谣言。本文更详细地分析了这些查询的执行。

NULL值如何存储在记录中

MySQL中的所有记录都有固定的格式。我们以InnoDB存储引擎Compact的行格式为例,看看d的值是否为NULL。在Compact行格式中,记录由以下部分组成: MySQL中IS NULL、IS NOT NULL、!=不能用索引?

以此类推,如果一个表有 9 个元素为 NULL,那么 NULL 值列表​​ 需要这条记录的 2 部分。来表示字节。

假设我们现在向表中插入一条记录 record_format_demo:

INSERT INTO record_format_demo(c1, c2, c3, c4)
    VALUES('eeee', 'fff', NULL, NULL);
复制代码

这条记录是 c1c1 , c4 数值​​这三列中的c3c4都是null,所以这三列对应的二进制位为:NULL值列表 表示为十六进制:0x06

带NULL键值的记录如何存储在B+树中

以InnoDB存储引擎为例,记录是以页的形式存储的(默认页大小为16KB),这些页B+树的节点组成一个这样的索引(下图只是B+树的例子,与上表无关): MySQL中IS NULL、IS NOT NULL、!=不能用索引?

。当记录A的id为521后,沿着记录的单向链表,沿着每条记录的next_record属性,直到某个key❀❀列❙

,即可获取该记录记录 A 不为 NULL。

Tips:通过B+树快速查找叶子节点记录的过程可以使用所谓的Page Directory来实现,但这不是本文的目的。您可以在手册中阅读。每一项都有详细解释。

不使用索引的依据是什么?

既然 IS NULL IS NOT NULL.必须使用!=什么时候必须使用全索引?那么表扫描呢?

答案很简单:成本。当然,如何量化使用给定索引执行查询的成本更为复杂,我们在这本小册子中花费了大量时间。但由于篇幅所限,我们这里仅进行定性分析。对于使用二级索引的查询,成本构成主要包括两个方面:

  • 读取二级索引记录的成本
  • 将二级索引记录检索到表中,即在聚集索引中查找操作成本是整条用户记录。

显然,要检查的二级索引记录越多,执行的表返回操作就越多。当达到一定比例时,使用二级索引执行查询的成本超过了扫描全表的成本(举个极端的例子,如果要扫描所有二级索引记录,就必须执行返表操作)对于每条记录,这当然不如直接扫描聚集索引那么快)。

因此,在MySQL优化器实际执行查询之前,它会预先计算每个可用索引要检查的二级索引记录的数量。例如,对于以下查询:

SELECT * FROM s1 WHERE key1 IS NULL;
复制代码

Optimizer 会分析此查询,只需找到值记录 Key1 nullnull,然后❀ -,然后 IDX_KEY1 ,看一下有多少条记录(如果符合二级索引记录的条数少,那么统计结果就准确,如果多了,就会用一定的工具来计算)模糊值。当然算法也比较复杂,就不详细说了,说明书上也有),这是优化器首先访问索引来计算之前要检查的索引记录条数的方法实际执行查询时,调用 index div当然,对于某些查询,比如WHERE子句中有IN条件,而IN条件包含很多参数,例如:

SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c', ... , 'zzzzzzz');
复制代码

,此时,区间where key1 value 必须统计的太多,所以无法使用索引潜水来访问二级索引idx_key1。相反,您必须使用幕后生成的一些统计数据来估计匹配。二级索引记录有多少条(显然根据统计数据估算记录数比索引潜水准确得多)。

无论你使用索引潜水还是根据统计数据估算,最终都会得到很多需要扫描的二级索引记录。如果这个数字占记录总数的比例特别大,那么你倾向于使用全表扫描来执行查询,否则你使用这个索引来执行查询。

明白了这一点就更容易理解为什么IS NULL NOT NULL、❀、❀❀❀仍然可以使用! WHERE 子句中的这些条件。 ,本质上优化器计算适当的二级索引数量与总记录数的比率。

不信谣,不传谣

正如你所看到的,在MySQL中选择不使用某个索引执行查询的依据很简单:成本相当小。而不是 WHERE 子句中的 IS NULLIS NOT NULL!=。请大家以后多多辟谣。没那么复杂,只是一个成本而已。

作者:小孩子4919
来源:掘金

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门