一些错误认知的更正 之前以为record header中的null的位图信息是这个字段是nullable,那么对应的位上就会设置成1,在extern中保存的是可变长度字段的设置的定义的大小,其实上面2个都不对,通过dump 块的信息,可以看到,在null的位图上,只有对应的字段值是null,才会设置成1,如果字段定义的是 nullable但实际的记录中有值,那么是有空间占用的,但是对应的位上是0. extern中的信息是可变字段的实际存储的最大长度.我的测试中是6个字符。 null bitmap的作用应该是在存储的时候,看对应的字段是否有值,没有值那么存储就会忽略这个字段,不占用空间。extern保留实际最大值的长度。dump信息见下方的nulls,为了方便,将null和 externs放在了一起,第一个字节是可变字段长度,第二个字节是null字段的bitmap测试的表记录如下
master [localhost] {msandbox} (test) > show create table dba_user5\G
*************************** 1. row ***************************
Table: dba_user5
Create Table: CREATE TABLE `dba_user5` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL COMMENT '用户名',
`class` varchar(100) DEFAULT NULL COMMENT 'class',
`account` int(11) DEFAULT NULL,
`version` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT
1 row in set (0.00 sec)
master [localhost] {msandbox} (test) > select * from dba_user5;
+----+----------+-------+---------+---------+
| id | username | class | account | version |
+----+----------+-------+---------+---------+
| 1 | cccccc | ll | NULL | 30 |
+----+----------+-------+---------+---------+
1 row in set (0.00 sec)
record header: {
"offset": 128,
"length": 5,
"next": 112,
"prev": 0,
"type": "conventional",
"heap_number": 2,
"n_owned": 0,
"info_flags": 0,
"offset_size": 0,
"n_fields": 0,
"nulls": "0000011000000100",
"lengths": null,
"externs": ""
下面开始正式测试 很多文章或开发规范都说,字段要设置成not null,很主要的原因是说省空间。真的如此吗?record header的信息我们已经清楚,下面看下record的内容。 首先看下 nullable的存储情况
master [localhost] {msandbox} (test) > show create table dba_user5\G
*************************** 1. row ***************************
Table: dba_user5
Create Table: CREATE TABLE `dba_user5` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL COMMENT '用户名',
`class` varchar(100) DEFAULT NULL COMMENT 'class',
`account` int(11) DEFAULT NULL,
`version` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT
1 row in set (0.00 sec)
master [localhost] {msandbox} (test) > select * from dba_user5;
+----+----------+-------+---------+---------+
| id | username | class | account | version |
+----+----------+-------+---------+---------+
| -5 | cccccc | ll | NULL | 30 |
+----+----------+-------+---------+---------+
1 row in set (0.00 sec)
解析看下
record header: {
"offset": 165,
"length": 5,
"next": 112,
"prev": 0,
"type": "conventional",
"heap_number": 3,
"n_owned": 0,
"info_flags": 0,
"offset_size": 0,
"n_fields": 0,
"nulls": "0000011000000100",
"lengths": null,
"externs": ""
}
offset 165
cluster key fileds == -5
transaction_id == 1732686
roll pointer == 55450570435854608
value1== cccccc
value2== ll
value3== 30
value4== 0
我们可以看到在允许为空的字段中,nulls有位图信息,并且null字段没有实际的存储。 下面看下not null的情况
master [localhost] {msandbox} (test) > show create table dba_user7\G
*************************** 1. row ***************************
Table: dba_user7
Create Table: CREATE TABLE `dba_user7` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) NOT NULL DEFAULT '' COMMENT '用户名',
`class` varchar(100) NOT NULL DEFAULT 'c' COMMENT 'class',
`account` int(11) NOT NULL DEFAULT '0',
`version` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT
1 row in set (0.00 sec)
master [localhost] {msandbox} (test) > select * from dba_user7;
+----+----------+-------+---------+---------+
| id | username | class | account | version |
+----+----------+-------+---------+---------+
| 1 | | ll | 0 | 30 |
+----+----------+-------+---------+---------+
1 row in set (0.00 sec)
看下解析结果,
record header: {
"offset": 127,
"length": 5,
"next": 112,
"prev": 0,
"type": "conventional",
"heap_number": 2,
"n_owned": 0,
"info_flags": 0,
"offset_size": 0,
"n_fields": 0,
"nulls": "0000001000000000",
"lengths": null,
"externs": ""
}
offset 127
cluster key fileds == 1
transaction_id == 1732701
roll pointer == 58828270628765968
value1== ll
value2== 0
value3== 30
value4== 0
先看下记录,由于username我们使用了默认值,空字符,实际上mysql并没有存储,account使用了默认的0还是进行了存储。并且在我的结果中,即使所有的字段都是not null,在null的bitmap上依然分配了一个字节,这个字节并没有取消。 总结:1 如果字符字段not null,并且默认值是空字符,那么实际存储这个字段不占用空间,如果默认字符非空,那么还是会占用存储空间,一个字符一个字节。如果字段是数值型的,那么使用默认值,也是占用空间。2 如果字段允许为空,并且字段中的值是null,那么这个字段实际不占用存储空间 3不管有没有为空的字段,mysql中都会至少有一个字节的null bitmap,所以字段小于9的,bitmap存储空间上都一样,空间上不会有节省。 所以,我们可以看到除非所有的字段都是设置成not null,并且字符字段默认值都是空字符,并且实际插入会用这个默认的空字符,才会省下一点空间,但实际的使用上,not null的字段一般都会插入具体的值。因此物理记录存储上not null没有节省很多空间 下面看下header,如果空字段数量超过9会使用2个字节存储null bitmap,以此类推。这么看在字段非常多的情况下,将字段设置成not null,会节省出来1,2个字节。 如果有32个字段是not null,那么可以省下4-1=3个字节,1000w记录的一个表,能省下30M空间,这点空间基本可以忽略了 从上面的测试来看,字段设置not null 节省空间的结论,表字段少,数量少情况下,基本可以忽略。表字段多,数量多,节省的空间还比较可观,另外关于null影响统计信息收集,执行计划的分析,暂时没有遇到具体案例,可能之前遇到过,当时没有仔细深入分析。有案例的同学可以提供下。测试版本5.7 有兴趣学习源码的加群一起学习啊 QQ: 700072075
