为什么mysql字段要使用NOT NULL


Posted in MySQL onMay 13, 2021

最近刚入职新公司,发现数据库设计有点小问题,数据库字段很多没有NOT NULL,对于强迫症晚期患者来说,简直难以忍受,因此有了这篇文章。

基于目前大部分的开发现状来说,我们都会把字段全部设置成NOT NULL并且给默认值的形式。

  • 通常,对于默认值一般这样设置:
  • 整形,我们一般使用0作为默认值。
  • 字符串,默认空字符串

时间,可以默认1970-01-01 08:00:01,或者默认0000-00-00 00:00:00,但是连接参数要添加zeroDateTimeBehavior=convertToNull,建议的话还是不要用这种默认的时间格式比较好

但是,考虑下原因,为什么要设置成NOT NULL?

来自高性能Mysql中有这样一段话:

尽量避免NULL

很多表都包含可为NULL(空值)的列,即使应用程序并不需要保存NULL也是如此,这是因为可为NULL是列的默认属性。通常情况下最好指定列为NOT NULL,除非真的需要存储NULL值。

如果查询中包含可为NULL的列,对MySql来说更难优化,因为可为NULL的列使得索引、索引统计和值比较都更复杂。可为NULL的列会使用更多的存储空间,在MySql里也需要特殊处理。当可为NULL的列被索引时,每个索引记录需要一个额外的字节,在MyISAM里甚至还可能导致固定大小的索引(例如只有一个整数列的索引)变成可变大小的索引。

通常把可为NULL的列改为NOT NULL带来的性能提升比较小,所以(调优时)没有必要首先在现有schema中查找并修改掉这种情况,除非确定这会导致问题。但是,如果计划在列上建索引,就应该尽量避免设计成可为NULL的列。

当然也有例外,例如值得一提的是,InnoDB使用单独的位(bit)存储NULL值,所以对于稀疏数据有很好的空间效率。但这一点不适用于MyISAM。

书中的描述说了几个主要问题,我这里暂且抛开MyISAM的问题不谈,这里我针对InnoDB作为考量条件。

  • 如果不设置NOT NULL的话,NULL是列的默认值,如果不是本身需要的话,尽量就不要使用NULL
  • 使用NULL带来更多的问题,比如索引、索引统计、值计算更加复杂,如果使用索引,就要避免列设置成NULL
  • 如果是索引列,会带来的存储空间的问题,需要额外的特殊处理,还会导致更多的存储空间占用
  • 对于稀疏数据又更好的空间效率,稀疏数据指的是很多值为NULL,只有少数行的列有非NULL值的情况

默认值

对于MySql而言,如果不主动设置为NOT NULL的话,那么插入数据的时候默认值就是NULL。

NULL和NOT NULL使用的空值代表的含义是不一样,NULL可以认为这一列的值是未知的,空值则可以认为我们知道这个值,只不过他是空的而已。

举个例子,一张表中的某一条name字段是NULL,我们可以认为不知道名字是什么,反之如果是空字符串则可以认为我们知道没有名字,他就是一个空值。

而对于大多数程序的情况而言,没有什么特殊需要非要字段要NULL的吧,NULL值反而会对程序造成比如空指针的问题。

对于现状大部分使用MyBatis的情况来说,我建议使用默认生成的insertSelective方法或者纯手动写插入方法,可以避免新增NOT NULL字段导致的默认值不生效或者插入报错的问题。

值计算

聚合函数不准确

对于NULL值的列,使用聚合函数的时候会忽略NULL值。

现在我们有一张表,name字段默认是NULL,此时对name进行count得出的结果是1,这个是错误的。

count(*)是对表中的行数进行统计,count(name)则是对表中非NULL的列进行统计。

为什么mysql字段要使用NOT NULL

=失效

对于NULL值的列,是不能使用=表达式进行判断的,下面对name的查询是不成立的,必须使用is NULL

为什么mysql字段要使用NOT NULL

与其他值运算

NULL和其他任何值进行运算都是NULL,包括表达式的值也是NULL。

user表第二条记录age是NULL,所以+1之后还是NULL,name是NULL,进行concat运算之后结果还是NULL。

为什么mysql字段要使用NOT NULL

可以再看下下面的例子,任何和NULL进行运算的话得出的结果都会是NULL,想象下你设计的某个字段如果是NULL还不小心进行各种运算,最后得出的结果。。。

为什么mysql字段要使用NOT NULL

distinct、group by、order by

对于distinctgroup by来说,所有的NULL值都会被视为相等,对于order by来说升序NULL会排在最前

为什么mysql字段要使用NOT NULL

其他问题

表中只有一条有名字的记录,此时查询名字!=a预期的结果应该是想查出来剩余的两条记录,会发现与预期结果不匹配。

为什么mysql字段要使用NOT NULL

索引问题

为了验证NULL字段对索引的影响,分别对nameage添加索引。

为什么mysql字段要使用NOT NULL

关于网上很多说如果NULL那么不能使用索引的说法,这个描述其实并不准确,根据引用官方文档[3]里描述,使用is NULL和范围查询都是可以和正常一样使用索引的,实际验证的结果好像也是这样,看以下例子。

为什么mysql字段要使用NOT NULL

然后接着我们往数据库中继续插入一些数据进行测试,当NULL列值变多之后发现索引失效了。

为什么mysql字段要使用NOT NULL

我们知道,一个查询SQL执行大概是这样的流程:

为什么mysql字段要使用NOT NULL

首先连接器负责连接到指定的数据库上,接着看看查询缓存中是否有这条语句,如果有就直接返回结果。

如果缓存没有命中的话,就需要分析器来对SQL语句进行语法和词法分析,判断SQL语句是否合法。

现在来到优化器,就会选择使用什么索引比较合理,SQL语句具体怎么执行的方案就确定下来了。

最后执行器负责执行语句、有无权限进行查询,返回执行结果。

从上面的简单测试结果其实可以看到,索引列存在NULL就会存在书中所说的导致优化器在做索引选择的时候更复杂,更加难以优化。

存储空间

数据库中的一行记录在最终磁盘文件中也是以行的方式来存储的,对于InnoDB来说,有4种行存储格式:REDUNDANTCOMPACTDYNAMICCOMPRESSED

InnoDB的默认行存储格式是COMPACT,存储格式如下所示,虚线部分代表可能不一定会存在。

为什么mysql字段要使用NOT NULL

变长字段长度列表:有多个字段则以逆序存储,我们只有一个字段所有不考虑那么多,存储格式是16进制,如果没有变长字段就不需要这一部分了。

NULL值列表:用来存储我们记录中值为NULL的情况,如果存在多个NULL值那么也是逆序存储,并且必须是8bit的整数倍,如果不够8bit,则高位补0。1代表是NULL,0代表不是NULL。如果都是NOT NULL那么这个就存在了。

ROW_ID:一行记录的唯一标志,没有指定主键的时候自动生成的ROW_ID作为主键。

TRX_ID:事务ID。

ROLL_PRT:回滚指针。

最后就是每列的值。

为了说明清楚这个存储格式的问题,我弄张表来测试,这张表只有c1字段是NOT NULL,其他都是可以为NULL的。

为什么mysql字段要使用NOT NULL

可变字段长度列表:c1c3字段值长度分别为1和2,所以长度转换为16进制是0x01 0x02,逆序之后就是0x02 0x01

NULL值列表:因为存在允许为NULL的列,所以c2,c3,c4分别为010,逆序之后还是一样,同时高位补0满8位,结果是00000010

其他字段我们暂时不管他,最后第一条记录的结果就是,当然这里我们就不考虑编码之后的结果了。

为什么mysql字段要使用NOT NULL

这样就是一个完整的数据行数据的格式,反之,如果我们把所有字段都设置为NOT NULL,并且插入一条数据a,bb,ccc,dddd的话,存储格式应该这样:

为什么mysql字段要使用NOT NULL

虽然我们发现NULL本身并不会占用存储空间,但是如果存在NULL的话就会多占用一个字节的标志位的空间。

文章参考文档:

https://dev.mysql.com/doc/refman/8.0/en/problems-with-null.html
https://dev.mysql.com/doc/refman/8.0/en/working-with-null.html
https://dev.mysql.com/doc/refman/5.6/en/is-null-optimization.html
https://dev.mysql.com/doc/refman/5.6/en/innodb-row-format.html
https://www.cnblogs.com/zhoujinyi/articles/2726462.html

到此这篇关于为什么mysql字段要使用NOT NULL的文章就介绍到这了,更多相关mysql字段使用NOT NULL内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL 角色(role)功能介绍
Apr 24 MySQL
MySQL 使用自定义变量进行查询优化
May 14 MySQL
Mysql 如何实现多张无关联表查询数据并分页
Jun 05 MySQL
mysql 直接拷贝data 目录下文件还原数据的实现
Jul 25 MySQL
Linux7.6二进制安装Mysql8.0.27详细操作步骤
Nov 27 MySQL
MySQL优化及索引解析
Mar 17 MySQL
MySQL时区造成时差问题
Apr 13 MySQL
WINDOWS下安装mysql 8.x 的方法图文教程
Apr 19 MySQL
MySQL 数据库范式化设计理论
Apr 22 MySQL
MySQL普通表如何转换成分区表
May 30 MySQL
MySQL选择合适的备份策略和备份工具
Jun 01 MySQL
Mysql中的触发器定义及语法介绍
Jun 25 MySQL
MySQL表字段时间设置默认值
May 13 #MySQL
MySql新手入门的基本操作汇总
May 13 #MySQL
MySQL中你可能忽略的COLLATION实例详解
浅谈mysql执行过程以及顺序
mysql 8.0.24版本安装配置方法图文教程
mysql 8.0.24 安装配置方法图文教程
MySQL 如何分析查询性能
May 12 #MySQL
You might like
php flv视频时间获取函数
2010/06/29 PHP
基于php-fpm 参数的深入理解
2013/06/03 PHP
PHP开发工具ZendStudio下Xdebug工具使用说明详解
2013/11/11 PHP
Yii查询生成器(Query Builder)用法实例教程
2014/09/04 PHP
PHP CURL中传递cookie的方法步骤
2019/05/09 PHP
JS 时间显示效果代码
2009/08/23 Javascript
关于jQuery中的end()使用方法
2011/07/10 Javascript
JS获取网页图片name属性的方法
2015/04/01 Javascript
javascript中动态函数用法实例分析
2015/05/14 Javascript
JavaScript实现横向滑出的多级菜单效果
2015/10/09 Javascript
Bootstrap Validator 表单验证
2016/07/25 Javascript
jsp 自动编译机制详细介绍
2016/12/01 Javascript
AngularJS  ng-repeat遍历输出的用法
2017/06/19 Javascript
让nodeJS支持ES6的词法----babel的安装和使用方法
2017/07/31 NodeJs
理解 Node.js 事件驱动机制的原理
2017/08/16 Javascript
jquery动态赋值id与动态取id方法示例
2017/08/21 jQuery
JS实现table表格固定表头且表头随横向滚动而滚动
2017/10/26 Javascript
解决vue-cli项目webpack打包后iconfont文件路径的问题
2018/09/01 Javascript
socket io与vue-cli的结合使用的示例代码
2018/11/01 Javascript
详解angular2 控制视图的封装模式
2018/12/27 Javascript
python基础教程之缩进介绍
2014/08/29 Python
Python多进程编程技术实例分析
2014/09/16 Python
python判断一个集合是否包含了另外一个集合中所有项的方法
2015/06/30 Python
使用XML库的方式,实现RPC通信的方法(推荐)
2017/06/14 Python
用tensorflow构建线性回归模型的示例代码
2018/03/05 Python
python队列queue模块详解
2018/04/27 Python
Django中在xadmin中集成DjangoUeditor过程详解
2019/07/24 Python
CSS3实现网站商品展示效果图
2020/01/18 HTML / CSS
Speedo速比涛中国官方网站:全球领先泳装运动品牌
2018/04/24 全球购物
高中军训感言600字
2014/03/11 职场文书
设计专业自荐信
2014/06/19 职场文书
2014年祖国生日寄语
2014/09/19 职场文书
2014年变电站工作总结
2014/12/19 职场文书
安全责任书
2015/01/29 职场文书
JavaScript如何利用Promise控制并发请求个数
2021/05/14 Javascript
Shell脚本一键安装Nginx服务自定义Nginx版本
2022/03/20 Servers