为什么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锁机制
Apr 05 MySQL
详解MySQL 用户权限管理
Apr 20 MySQL
MySQL时间设置注意事项的深入总结
May 06 MySQL
一看就懂的MySQL的聚簇索引及聚簇索引是如何长高的
May 25 MySQL
MySQL查看表和清空表的常用命令总结
May 26 MySQL
MySQL常见优化方案汇总
Jan 18 MySQL
Mysql分库分表之后主键处理的几种方法
Feb 15 MySQL
浅谈redis的过期时间设置和过期删除机制
Mar 18 MySQL
MySQL派生表联表查询实战过程
Mar 20 MySQL
MySQL聚簇索引和非聚簇索引的区别详情
Jun 14 MySQL
MySQL数据库表约束讲解
Jun 21 MySQL
MySQL实现字段分割一行转多行的示例代码
Jul 07 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)
2013/06/21 PHP
ThinkPHP里用U方法调用js文件实例
2015/06/18 PHP
JavaScript 利用StringBuffer类提升+=拼接字符串效率
2009/11/24 Javascript
javascript中的循环语句for语句深入理解
2014/04/04 Javascript
深入理解JS正则表达式---分组
2016/07/18 Javascript
jQuery实现页面点击后退弹出提示框的方法
2016/08/24 Javascript
使用ionic切换页面卡顿的解决方法
2016/12/16 Javascript
js实现下一页页码效果
2017/03/07 Javascript
Node.js如何响应Ajax的POST请求并且保存为JSON文件详解
2017/03/10 Javascript
JavaScript实现简单精致的图片左右无缝滚动效果
2017/03/16 Javascript
微信小程序 slider的简单实例
2017/04/19 Javascript
JavaScript之Date_动力节点Java学院整理
2017/06/28 Javascript
node.js-v6新版安装具体步骤(分享)
2017/09/06 Javascript
详解bootstrap用dropdown-menu实现上下文菜单
2017/09/22 Javascript
利用JS判断客户端类型你应该知道的四种方法
2017/12/22 Javascript
如何快速解决JS或Jquery ajax异步跨域的问题
2018/01/08 jQuery
浅谈实现vue2.0响应式的基本思路
2018/02/13 Javascript
使用Angular CLI从蓝本生成代码详解
2018/03/24 Javascript
element 中 el-menu 组件的无限极循环思路代码详解
2020/04/26 Javascript
vue父子组件间引用之$parent、$children
2020/05/20 Javascript
python实现多线程采集的2个代码例子
2014/07/07 Python
Python 字典dict使用介绍
2014/11/30 Python
python、java等哪一门编程语言适合人工智能?
2017/11/13 Python
Python搭建FTP服务器的方法示例
2018/01/19 Python
python使用udp实现聊天器功能
2018/12/10 Python
手把手教你如何用Pycharm2020.1.1配置远程连接的详细步骤
2020/08/07 Python
Python logging模块原理解析及应用
2020/08/13 Python
Python图像处理之膨胀与腐蚀的操作
2021/02/07 Python
美国最大的珠宝首饰网上商城:Jewelry.com
2016/07/22 全球购物
FOREO斐珞尔官方旗舰店:LUNA露娜洁面仪
2018/03/11 全球购物
资产评估专业学生的自我鉴定
2013/11/14 职场文书
《藤野先生》教学反思
2014/02/19 职场文书
节约用水标语
2014/06/11 职场文书
社区服务标语
2014/07/01 职场文书
行政助理岗位职责范本
2015/04/11 职场文书
IDEA 链接Mysql数据库并执行查询操作的完整代码
2021/05/20 MySQL