为什么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之DML语言
Apr 05 MySQL
一篇文章弄懂MySQL查询语句的执行过程
May 07 MySQL
MySQL 8.0 之不可见列的基本操作
May 20 MySQL
带你学习MySQL执行计划
May 31 MySQL
你知道哪几种MYSQL的连接查询
Jun 03 MySQL
详解MySQL中的pid与socket
Jun 15 MySQL
Mysql事务索引知识汇总
Mar 17 MySQL
MYSQL优化之数据表碎片整理详解
Apr 03 MySQL
mysql 体系结构和存储引擎介绍
May 06 MySQL
MySQL查询日期时间
May 15 MySQL
MySQL运行报错:“Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggre”解决方法
Jun 14 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
在WINDOWS中设置计划任务执行PHP文件的方法
2011/12/19 PHP
解析WordPress中函数钩子hook的作用及基本用法
2015/12/22 PHP
精解window.setTimeout()&window.setInterval()使用方式与参数传递问题!
2007/11/23 Javascript
jquery text()要注意啦
2009/10/30 Javascript
ASP.NET jQuery 实例16 通过控件CustomValidator验证RadioButtonList
2012/02/03 Javascript
JavaScript获取网页表单action属性的方法
2015/04/02 Javascript
sencha ext js 6 快速入门(必看)
2016/06/01 Javascript
NodeJS和BootStrap分页效果的实现代码
2016/11/07 NodeJs
Boostrap栅格系统与自己额外定义的媒体查询的冲突问题
2017/02/19 Javascript
JS中去掉array中重复元素的方法
2017/05/26 Javascript
Windows下安装 node 的版本控制工具 nvm
2020/02/06 Javascript
[07:40]DOTA2每周TOP10 精彩击杀集锦vol.4
2014/06/25 DOTA
Python yield使用方法示例
2013/12/04 Python
python查找第k小元素代码分享
2013/12/18 Python
Python实现导出数据生成excel报表的方法示例
2017/07/12 Python
python实现二叉树的遍历
2017/12/11 Python
Python爬虫常用库的安装及其环境配置
2018/09/19 Python
Python实现Dijkstra算法
2018/10/17 Python
Python3.7 新特性之dataclass装饰器
2019/05/27 Python
Python模块的定义,模块的导入,__name__用法实例分析
2020/01/07 Python
Python进程Multiprocessing模块原理解析
2020/02/28 Python
什么是python的函数体
2020/06/19 Python
CSS3教程(3):border-color网页边框色彩
2009/04/02 HTML / CSS
实例教程 一款纯css3实现的数字统计游戏
2014/11/10 HTML / CSS
加拿大床上用品、家居装饰、厨房和浴室产品购物网站:Linen Chest
2018/06/05 全球购物
Tea Collection官网:一家位于旧金山的童装公司
2020/08/07 全球购物
会计毕业生自我鉴定
2013/11/04 职场文书
法律专业实习鉴定
2013/12/22 职场文书
新员工入职感言
2014/02/01 职场文书
教师个人自我评价范文
2014/04/13 职场文书
捐款倡议书怎么写
2014/05/13 职场文书
培训师岗位职责
2015/02/14 职场文书
解决Goland 同一个package中函数互相调用的问题
2021/05/06 Golang
python脚本框架webpy模板赋值实现
2021/11/20 Python
Win11怎样将锁屏账户头像图片改成动画视频
2021/11/21 数码科技
请求模块urllib之PYTHON爬虫的基本使用
2022/04/08 Python