数据库表字段为何默认为 NOT NULL?
wptr33 2024-12-28 15:58 15 浏览
目前大部分的开发现状来说,我们都会把字段全部设置成 NOT NULL 并且给默认值的形式。
最近在 Review 代码时候,仍然偶尔发现数据库字段很多没有设置 NOT NULL,为什么要设置成 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。
本文主要对表字段为 NOT NULL 情况的应用影响汇总。
一、NOT NULL 的性能优势
数据库表字段设置为 NOT NULL 在性能方面具有诸多优势。
(1)查询优化是一个重要方面
当一列被标记为 NOT NULL 时,数据库系统可以使用这个信息来优化查询。因为系统知道这一列的值永远不会为空,所以在执行查询时可以忽略 NULL 值,从而提高查询性能。例如,在一个包含数百万条记录的大型数据库中,如果某列被设置为 NOT NULL,那么在查询这一列的值时,数据库系统可以直接忽略所有的 NULL 值,极大地提高了查询速度。
(2)NOT NULL 可以减少存储空间占用
NULL 列需要更多的存储空间,因为需要一个额外字节作为判断是否为 NULL 的标志位。如果把一些可填可不填的字段设置为 NOT NULL,就可以节省这些额外的存储空间。例如,假设有一个包含大量记录的表,其中有多个可填可不填的字段,如果这些字段都设置为 NOT NULL,那么随着记录数量的增加,节省的存储空间会非常可观。
(3)索引效率也会得到提升
索引含有空值的列很难进行查询优化,而且对表索引时不会存储 NULL 值。所以如果索引的字段可以为 NULL 值,索引的效率会下降,因为它们使得索引、索引的统计信息以及比较运算更加复杂。应该用 0、一个特殊的值或者一个空串代替 NULL 值。
综上所述,数据库表字段设置为 NOT NULL 在性能方面具有显著优势,可以提高查询速度、减少存储空间占用和提升索引效率。
二、对开发的友好性
(1)简化代码逻辑
数据库表字段设置为 NOT NULL,可以极大地简化开发人员的代码逻辑。
在实际开发中,如果字段允许为 NULL,那么开发人员在处理这些字段时,需要进行大量的空值判断。
例如,在 Java 语言中,如果实体类的某个字段允许为 NULL,那么在使用这个字段进行操作时,开发人员需要不断地进行空指针检查,以避免出现空指针异常。这不仅增加了代码的复杂度,还降低了代码的可读性和可维护性。
(2)提高数据一致性
NOT NULL 约束能够在数据库层面强制实施数据一致性约束,从而减少数据质量问题。
当数据库表中的某一列被设置为 NOT NULL 时,这意味着这一列的每一行都必须有值。这样可以确保数据的完整性和一致性,避免出现数据不完整或不一致的情况。
例如,在一个电商系统中,如果用户表中的用户名、邮箱等关键信息字段被设置为 NOT NULL,那么在用户注册和登录时,系统可以确保这些关键信息都被正确地填写,从而提高数据的质量和可靠性。此外,NOT NULL 约束还可以防止开发人员在插入或更新数据时出现错误。
三、应用注意事项
为了更好的描述应用注意事项,我们初始化原始数据,一个只有1列的表,很简单:
3.1 聚合函数
常见的聚合函数有 count、min、max、avg 以及 sum,这些聚合函数在遇到 NULL 值时,处理方式各不相同:
- max、min、avg 和 sum 函数对 NULL 值采取的处理方式是直接忽略
- count 函数处理 NULL 值则需要分情况进行讨论
- count () 返回的是所有记录的总和,含有 NULL 值的记录不会被忽略,也会被计算在内;
- count (column_name) 如果这个列名中含有一个值为 NULL,则该条记录会被忽略,此时的返回值为 count ()-1
3.2 与其他值运算规则
在数据库中,NULL 和其他任何值进行运算的结果都是 NULL,会给数据处理带来了很大的不确定性:
- 当进行加法运算时,如果其中一个值为 NULL,那么结果也为 NULL
- 在进行乘法、除法等其他运算时,只要有一个操作数为 NULL,结果就会是 NULL
这种特性使得在处理包含可能为 NULL 值的字段时,需要特别小心,否则很容易得到错误的结果。
假设我们有一个数据库表,其中包含两个字段 A 和 B,A 字段的值可能为 NULL,B 字段的值为固定值。当我们尝试进行 A+B 的运算时,如果 A 的值为 NULL,那么结果也会是 NULL,而不是我们期望的 B 的值。
3.3 对 distinct、group by、order by 的影响
在数据库操作中,对于 distinct 和 group by 来说,所有的 NULL 值都会被视为相等。这意味着如果在进行数据去重或者分组操作时,含有 NULL 值的记录会被归为一类。
对于 order by 来说,升序时 NULL 会排在最前。这是因为在排序过程中,数据库系统将 NULL 视为一个特殊的值,按照特定的规则进行排序。
当使用 distinct 对这个字段进行去重操作时,所有的 NULL 值会被视为同一个值,只显示一次。同样,在使用 group by 进行分组操作时,NULL 值的记录会被分到同一组中。
四、其他影响问题
4.1 索引问题
索引列中存在大量 NULL 值可能会导致索引失效,影响查询性能。因此,将数据库表字段设置为 NOT NULL 可以减少这种情况的发生,提高索引的有效性和查询性能。
(1)索引使用准确性
在网上有很多说法认为 NULL 不能使用索引,然而这种说法并不完全准确。实际上,索引列中存在 NULL 值并不意味着完全不能使用索引,只是会使数据库的优化器在选择索引时变得更加复杂。
查询条件针对的是索引列中的非 NULL 值,数据库可以使用索引进行快速查找。但是,如果查询条件涉及到 NULL 值的判断,如 IS NULL 或 IS NOT NULL,优化器可能需要考虑更多的因素来决定是否使用索引以及如何使用索引。
因为 NULL 值在数据库中被视为未知的状态,与其他具体的值不同,所以在处理包含 NULL 值的索引列时,优化器需要评估各种情况,包括索引的选择性、数据的分布等,以确定最佳的查询执行计划。这就可能导致在某些情况下,优化器选择不使用索引,而采用全表扫描等其他方式来执行查询。
(2)索引失效情况
如果索引列上存在大量的 NULL 值,数据库可能会认为使用索引并不能显著提高查询性能,因此选择不使用索引。
假设一个表,其中某个索引列上有很多 NULL 值。当进行查询时,如果查询条件涉及到这个索引列,数据库可能会发现使用索引进行查找并不能有效地减少需要扫描的数据量,因为大量的 NULL 值使得索引的选择性降低。在这种情况下,数据库可能会选择进行全表扫描,而不是使用索引。
此外,当索引列上的 NULL 值过多时,还可能影响索引的统计信息。数据库通常会根据索引的统计信息来评估查询的执行计划,如果统计信息不准确,可能会导致优化器做出错误的决策。
4.2 存储空间考虑
数据库中的一行记录在最终磁盘文件中是以行的方式来存储的,对于 InnoDB 来说,有 4 种行存储格式:REDUNDANT、COMPACT、DYNAMIC 和 COMPRESSED。
InnoDB 的默认行存储格式是 COMPACT,存储格式如下所示,虚线部分代表可能不一定会存在。
(1)存储格式详解
- 变长字段长度列表:有多个字段则以逆序存储,存储格式是 16 进制,如果没有变长字段就不需要这一部分了。
- NULL 值列表:用来存储我们记录中值为 NULL 的情况,如果存在多个 NULL 值那么也是逆序存储,并且必须是 8bit 的整数倍,如果不够 8bit,则高位补 0。1 代表是 NULL,0 代表不是 NULL。如果都是 NOT NULL 那么这个就不存在了。
- ROW_ID:一行记录的唯一标志,没有指定主键的时候自动生成的 ROW_ID 作为主键。
- TRX_ID:事务 ID。
- ROLL_PRT:回滚指针。
- 每列的值。
(2)NOT NULL 对存储空间的影响
如果存在允许为 NULL 的列,就会多占用一个字节的标志位空间。
假设有一张表,只有一个字段允许为 NULL,其他字段都是 NOT NULL。当存储一条记录时,如果这个可空字段的值为 NULL,那么就需要在 NULL 值列表中进行标记,并且在记录头部占用一个额外的字节作为标志位。随着记录数量的增加,这种额外的存储空间占用也会逐渐累积。
然而,如果将所有字段都设置为 NOT NULL,就不会有 NULL 值列表和标志位的占用。例如,插入一条数据,所有字段都有确定的值,存储格式更加紧凑,不会有额外的空间浪费。
综上所述,将数据库表字段设置为 NOT NULL 可以减少存储空间的占用,使数据库的存储更加高效。特别是在处理大量数据时,这种节省空间的效果会更加明显。
·END·
希望今天的讲解对大家有所帮助,谢谢!
Thanks for reading!
作者:张张,十年研发风雨路,大厂架构师,「架构精进之路」专注架构技术沉淀学习及分享,职业与认知升级,坚持分享接地气儿的干货文章,期待与你一起成长。
关注并私信我回复“01”,送你一份程序员成长进阶大礼包,欢迎勾搭。
相关推荐
- Linux高性能服务器设计
-
C10K和C10M计算机领域的很多技术都是需求推动的,上世纪90年代,由于互联网的飞速发展,网络服务器无法支撑快速增长的用户规模。1999年,DanKegel提出了著名的C10问题:一台服务器上同时...
- 独立游戏开发者常犯的十大错误
-
...
- 学C了一头雾水该咋办?
-
学C了一头雾水该怎么办?最简单的方法就是你再学一遍呗。俗话说熟能生巧,铁杵也能磨成针。但是一味的为学而学,这个好像没什么卵用。为什么学了还是一头雾水,重点就在这,找出为什么会这个样子?1、概念理解不深...
- C++基础语法梳理:inline 内联函数!虚函数可以是内联函数吗?
-
上节我们分析了C++基础语法的const,static以及this指针,那么这节内容我们来看一下inline内联函数吧!inline内联函数...
- C语言实战小游戏:井字棋(三子棋)大战!文内含有源码
-
井字棋是黑白棋的一种。井字棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙、三子旗等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。但是,有很多时...
- C++语言到底是不是C语言的超集之一
-
C与C++两个关系亲密的编程语言,它们本质上是两中语言,只是C++语言设计时要求尽可能的兼容C语言特性,因此C语言中99%以上的功能都可以使用C++完成。本文探讨那些存在于C语言中的特性,但是在C++...
- 在C++中,如何避免出现Bug?
-
C++中的主要问题之一是存在大量行为未定义或对程序员来说意外的构造。我们在使用静态分析器检查各种项目时经常会遇到这些问题。但正如我们所知,最佳做法是在编译阶段尽早检测错误。让我们来看看现代C++中的一...
- ESL-通过事件控制FreeSWITCH
-
通过事件提供的最底层控制机制,允许我们有效地利用工具箱,适时选择使用其中的单个工具。FreeSWITCH是一个核心交换与混合矩阵,它周围有几十个模块提供各种功能特性。我们完全控制了所有的即时信息,这些...
- 物理老师教你学C++语言(中篇)
-
一、条件语句与实验判断...
- C语言入门指南
-
当然!以下是关于C语言入门编程的基础介绍和入门建议,希望能帮你顺利起步:C语言入门指南...
- C++选择结构,让程序自动进行决策
-
什么是选择结构?正常的程序都是从上至下顺序执行,这就是顺序结构...
- C++特性使用建议
-
1.引用参数使用引用替代指针且所有不变的引用参数必须加上const。在C语言中,如果函数需要修改变量的值,参数必须为指针,如...
- C++程序员学习Zig指南(中篇)
-
1.复合数据类型结构体与方法的对比C++类:...
- 研一自学C++啃得动吗?
-
研一自学C++啃得动吗?在开始前我有一些资料,是我根据网友给的问题精心整理了一份「C++的资料从专业入门到高级教程」,点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!个人...
- C++关键字介绍
-
下表列出了C++中的常用关键字,这些关键字不能作为变量名或其他标识符名称。1、autoC++11的auto用于表示变量的自动类型推断。即在声明变量的时候,根据变量初始值的类型自动为此变量选择匹配的...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mysql max (33)
- vba instr (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)