为什么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 07 MySQL
MySQL创建索引需要了解的
Apr 08 MySQL
MySQL性能压力基准测试工具sysbench的使用简介
Apr 21 MySQL
MySQL之高可用集群部署及故障切换实现
Apr 22 MySQL
MySQL sql_mode修改不生效的原因及解决
May 07 MySQL
Mysql效率优化定位较低sql的两种方式
May 26 MySQL
为什么MySQL分页用limit会越来越慢
Jul 25 MySQL
使用ORM新增数据在Mysql中的操作步骤
Jul 26 MySQL
SQL实现LeetCode(180.连续的数字)
Aug 04 MySQL
MySQL对数据表已有表进行分区表的实现
Nov 01 MySQL
优化Mysql查询的示例
Apr 26 MySQL
MySQL 数据库 增删查改、克隆、外键 等操作
May 11 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安全配置方法
2007/06/16 PHP
php gzip压缩输出的实现方法
2013/04/27 PHP
PHP中shuffle数组值随便排序函数用法
2014/11/21 PHP
PHP实现数据分页显示的简单实例
2016/05/26 PHP
详解PHP函数 strip_tags 处理字符串缺陷bug
2017/06/11 PHP
Extjs4.0 ComboBox如何实现三级联动
2016/05/11 Javascript
JavaScript的String字符串对象常用操作总结
2016/05/26 Javascript
JavaScript使用Range调色及透明度实例
2016/09/25 Javascript
基于jQuery和Bootstrap框架实现仿知乎前端动态列表效果
2016/11/09 Javascript
详解React开发必不可少的eslint配置
2018/02/05 Javascript
Vue监听数据渲染DOM完以后执行某个函数详解
2018/09/11 Javascript
vue-router结合vuex实现用户权限控制功能
2019/11/14 Javascript
浅谈vue权限管理实现及流程
2020/04/23 Javascript
vue实现放大镜效果
2020/09/17 Javascript
JavaScript 中的执行上下文和执行栈实例讲解
2021/02/25 Javascript
Numpy数据类型转换astype,dtype的方法
2018/06/09 Python
如何更优雅地写python代码
2019/07/02 Python
python中用logging实现日志滚动和过期日志删除功能
2019/08/20 Python
python字典key不能是可以是啥类型
2020/08/04 Python
Python如何读写CSV文件
2020/08/13 Python
Python之多进程与多线程的使用
2021/02/23 Python
设计师家具购买和委托在线市场:Viyet
2016/11/16 全球购物
Bealls Florida百货商店:生活服饰、家居装饰和鞋子
2018/02/23 全球购物
Wiggle新西兰:自行车、跑步、游泳
2020/05/06 全球购物
毕业生就业自荐信
2013/12/04 职场文书
测量工程专业求职信
2014/02/24 职场文书
三八红旗手先进事迹材料
2014/05/13 职场文书
求职信范文大全
2014/05/26 职场文书
中学生检讨书1000字
2014/10/28 职场文书
班主任自我评价范文
2015/03/11 职场文书
2015年母亲节寄语
2015/03/23 职场文书
西游降魔篇观后感
2015/06/15 职场文书
postgresql使用filter进行多维度聚合的解决方法
2021/07/16 PostgreSQL
JavaScript事件的委托(代理)的用法示例详解
2022/02/18 Javascript
「魔导具师妲莉亚永不妥协~从今天开始的自由职人生活~」1、2卷发售宣传CM公开
2022/03/21 日漫
我国拿下天问一号火星着陆区附近 22 个地理实体命名:平乐、西柏坡、古田、漠河等
2022/04/29 数码科技