目前大部分的开发现状来说,我们都会把字段全部设置成 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 可以减少存储空间的占用,使数据库的存储更加高效。特别是在处理大量数据时,这种节省空间的效果会更加明显。