MySQL字段究竟是否需要设置成not null

来源:这里教程网 时间:2026-03-01 16:49:51 作者:

一些错误认知的更正 之前以为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

相关推荐