为什么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 22 MySQL
详解MySQL主从复制及读写分离
May 07 MySQL
MySQL如何构建数据表索引
May 13 MySQL
MySQL主从搭建(多主一从)的实现思路与步骤
May 13 MySQL
MySQL库表名大小写的选择
Jun 05 MySQL
MySQL系列之开篇 MySQL关系型数据库基础概念
Jul 02 MySQL
MySQL基础快速入门知识总结(附思维导图)
Sep 25 MySQL
深入讲解数据库中Decimal类型的使用以及实现方法
Feb 15 MySQL
详解MySQL的主键查询为什么这么快
Apr 03 MySQL
MySQL库表太大怎么办? 数据库分库分表项目实践
Apr 11 MySQL
分析MySQL优化 index merge 后引起的死锁
Apr 19 MySQL
MySQL 逻辑备份 into outfile
May 15 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
CentOS下搭建PHP环境与WordPress博客程序的全流程总结
2016/05/07 PHP
PHP socket 模拟POST 请求实例代码
2016/07/18 PHP
laravel框架语言包拓展实现方法分析
2019/11/22 PHP
利用浏览器全屏api实现js全屏
2014/01/16 Javascript
JavaScript实现的in_array函数
2014/08/27 Javascript
Lab.js初次使用笔记
2015/02/28 Javascript
jQuery中 prop() attr()使用详解
2015/05/19 Javascript
jquery实现华丽的可折角广告代码
2015/09/02 Javascript
JavaScript电子时钟倒计时第二款
2016/01/10 Javascript
学习JavaScript设计模式之单例模式
2016/01/19 Javascript
jQuery模仿单选按钮选中效果
2016/06/24 Javascript
关于Javascript回调函数的一个妙用
2016/08/29 Javascript
基于input框覆盖掉数字英文的实例讲解
2017/07/21 Javascript
基于substring()和substr()的使用以及区别(实例讲解)
2017/12/28 Javascript
jQuery实现图片简单轮播功能示例
2018/08/13 jQuery
小程序的上传文件接口的注意要点解析
2019/09/17 Javascript
vue中使用vee-validator完成表单校验方案
2019/11/01 Javascript
vue.js 实现a标签href里添加参数
2019/11/12 Javascript
javascript实现切割轮播效果
2019/11/28 Javascript
vue3.0 项目搭建和使用流程
2021/03/04 Vue.js
[01:38]DOTA2 2015国际邀请赛中国区预选赛 Showopen
2015/06/01 DOTA
[54:45]2018DOTA2亚洲邀请赛 4.1 小组赛 A组 Optic vs OG
2018/04/02 DOTA
Python logging模块学习笔记
2014/05/24 Python
python中日期和时间格式化输出的方法小结
2015/03/19 Python
使用python中的in ,not in来检查元素是不是在列表中的方法
2018/07/06 Python
Django 导出项目依赖库到 requirements.txt过程解析
2019/08/23 Python
Django 对IP访问频率进行限制的例子
2019/08/30 Python
python下载库的步骤方法
2019/10/12 Python
如何利用pygame实现简单的五子棋游戏
2019/12/29 Python
python爬虫中采集中遇到的问题整理
2020/11/27 Python
KIKO美国官网:意大利的平价彩妆品牌
2017/05/16 全球购物
2014年高三班主任工作总结
2014/12/05 职场文书
公司周年庆典致辞
2015/07/30 职场文书
2015年度学校应急管理工作总结
2015/10/22 职场文书
CSS 实现多彩、智能的阴影效果
2021/05/12 HTML / CSS
Tomcat用户管理的优化配置详解
2022/03/31 Servers