暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

关于mysql中的null

辣肉面加蛋加素鸡 2021-08-06
1600

null可能引起的问题

先简单看几个使用null引起的问题

构建测试数据:

create table test1 (id int , name varchar(20) not null);

create table test2 (id int , name varchar(20) );

insert into test1 (id,name )values (1,'tom'),(2,'jerry'),(3,'peter');

insert into test2 (id,name )values (1,'tom'),(2,'jerry'),(3,null);

1. 子查询有null的话,返回永远为空结果
select * from test1 where name not in (select name from test2 where id!=1);


如果test1和test2互换,则能正常查出结果:



2. count统计的时候,null不会计入统计结果


应该有三条记录

3. 列值允许为空,索引不存储null值,结果集中不会包含这些记录


应该返回2,3两行,因为3的name为null,所以只返回2

4. null会占用额外的空间


key_len表示所选择的索引长度有多少字节,test1不包含null值,所选的长度为62


test2的name字段是含有null的,所以explain的时候key_len为63,null需要额外的一个标示位,占用1字节

除了以上几个null可能引起的问题,大家最关心的应该是如果表中有null值,那么IS NULL、IS NOT NULL这些条件能不能用到索引,是不是一旦出现了就会全表扫描。

NULL columns require additional space in the rowto record whether their values are NULL. For MyISAM tables, each NULL columntakes one bit extra, rounded up to the nearest byte.

这是高性能mysql书中的一段话,意思是mysql难以优化null的查询,它会使索引、索引统计计算更为复杂。null列需要额外的空间,还需要mysql内部进行处理。

说得很模糊,具体有没有影响还得从null的存储结构看起。

null是怎么存储的

null值在记录中的存储

这里只讨论innodb引擎下的compact行格式,因为生产一般都用compact


先看下compact格式示意图:


再创建一张模拟表:

CREATE TABLE null_test (
c1 VARCHAR(5),
c2 VARCHAR(5) NOT NULL,
c3 CHAR(5),
c4 VARCHAR(5)
) CHARSET=utf8 ROW_FORMAT=COMPACT;

记录null值主要分为以下几个步骤:

  1. null列的统计:

    我们知道主键列和not null列都不无法存储null的,所以null_test表只有c1、c3、c4三列会被记录到统计表中。

  2. null列的二进制位关系:

    null_test表有三列可以为null,这三列与二进制位的关系如下


    二进制位按照列的顺序逆序排列,所以第一个列c1对应最后一个二进制位。

    当二进制位的值为1时,表示该列的值是null,当二进制位的值是0时,表示该列的值不为null。

  3. null列补位:

    NULL值列表必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补0。null_test表有3个null列,不足一字节,所以要在高位补0,就变成了这样


    若一个表有9个以上的null列,那null记录表就需要至少两字节来表示了。

现在给测试表插入一条记录:

INSERT INTO null_test (c1, c2, c3, c4)
VALUES('ok', 'fine', NULL, NULL);

这样二进制位的情况变为:


null值在b+树种的存放

在innodb存储引擎中,记录都是存储在页面中的,这些页面可以作为B+树的节点而组成一个索引。聚簇索引和二级索引一样,有多少个索引,就有多少颗b+树。

对于聚簇索引索引来说,页面中的记录是按照主键值进行排序的;而对于二级索引来说,页面中的记录是按照给定的索引列的值进行排序的。

另外对于聚簇索引来说,B+树叶子节点对应的页面中存储的是完整的用户记录(就是一条记录中包含我们定义的所有列值,还包含一些InnoDB自己添加的一些隐藏列);而对于二级索引来说,B+树叶子节点对应的页面中存储的只是索引列的值 + 主键值。

简单说了下聚簇索引和二级索引的构成区别。

聚簇索引是不能有包含null的,就是说一条记录的主键值不允许存储null值:


explain该语句,执行计划显示Impossible WHERE,MySQL优化器直接判定where子句为null,都不会去执行它。

但是对于二级索引来说,索引列的值时可能为null的。对于索引列值为NULL的二级索引记录来说,它是被放在b+树的最左边

CREATE TABLE `test_null` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sum` int(11) DEFAULT NULL,
`mark` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_sum` (`sum`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


insert into test_null (id,sum,mark) values (8,100,'tom'),(88,200,'jerry'),(888,null,'peter'),(8888,null,'alice');


还是模拟一张测试表,再看下索引布局的示意图:


从图中可以看出,对于test_null表的二级索引idx_sum来说,值为NULL的二级索引记录都被放在了B+树的最左边,mysql release notes里有句话

We define the SQL null to be the smallest possible value of a field.

意思就是SQL中的NULL值被认为是列中最小的值。

如果有条查询null值得sql:

select * from test_null where sum is null;

它的处理过程就是,通过二级索引idx_sum对应的B+树快速定位到叶子节点中符合条件的最左边的那条记录后,也就是本例中id值为888的那条记录之后,就可以顺着每条记录都有的next_record属性沿着由记录组成的单向链表去获取记录了,直到某条记录的sum列不为NULL(sum=100时停止)。

涉及null时,是不是用索引的依据


沿用上面的测试表,查询条件是 is null,从执行计划来看是走了索引。那数据库走不走索引的依据到底是什么?

对于一个使用二级索引的查询来说,判断用不用索引有以下两个方面:

  • 读取二级索引的成本

  • 将二级索引记录执行回表操作,也就是到聚簇索引中找到完整的用户记录的操作所付出的成本

要扫描的二级索引记录条数越多,那么需要执行的回表操作的次数也就越多,达到了某个比例时,使用二级索引执行查询的成本也就超过了全表扫描的成本。比方说要扫描的全部的二级索引记录,那就要对每条记录执行一遍回表操作,自然不如直接扫描聚簇索引来的快。

所以MySQL优化器在真正执行查询之前,对于每个可能使用到的索引来说,都会预先计算一下需要扫描的二级索引记录的数量。

select * from test_null where sum is null;

优化器会分析出此查询只需要查找sum值为NULL的记录,然后访问一下二级索引idx_sum,看一下值为NULL的记录有多少。这种在查询真正执行前优化器先访问索引来计算需要扫描的索引记录数量的方式称之为index dive。

还有种方式叫统计估算,比如有个查询:

select * from test_null where sum in ('100','200','300',......,'nnnnn');

这样的话需要统计的key1值所在的区间就太多了,这样就不能采用index dive的方式去真正的访问二级索引idx_sum,而是需要采用之前在背地里产生的一些统计数据去估算匹配的二级索引记录有多少条。当然啦,这种方式比起index dive性能要差好多。

反正不论采用index dive还是依据统计数据估算,最终要得到一个需要扫描的二级索引记录条数,如果这个条数占整个记录条数的比例特别大,那么就趋向于使用全表扫描执行查询,否则趋向于使用这个索引执行查询。

总结

使用null值,会对影响查询结果有些影响,但是这时可以通过预发去规避的。null还会占用额外的存储空间和计算消耗,对于体量不多的业务来说,这些代价也是可以忽略不计的。除此以外,对于null的判断在mysql优化器判断合理的前提下也是会使用包含null值的索引的。

总的来说,不用null最好,但是有些情景下使用null能更便捷地处理业务,那启用null也是可以的,用于不用还是要根据实际业务实现来判断。


文章转载自辣肉面加蛋加素鸡,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论