为什么我们不应该允许NULL?


125

我记得读过一篇有关数据库设计的文章,并且我还记得它说您应该具有NOT NULL的字段属性。我不记得为什么会这样。

我似乎只能想到的是,作为应用程序开发人员,您无需测试NULL 可能不存在的数据值(例如,字符串的空字符串)。

但是,对于日期,日期时间和时间,该怎么办(SQL Server 2008)?您必须使用一些历史性的或触底反弹的日期。

有什么想法吗?


4
这个答案对NULL的用法有深刻见解dba.stackexchange.com/questions/5176/…–
Derek Downey

10
真?如果我们不应该使用RDBMS,为什么它完全允许我们使用NULL?只要您知道如何处理NULL,NULL就没有错。
Fr0zenFyr 2013年

3
这是BI数据建模吗?通常,您不应该在事实表中允许使用null。否则,正确使用null就是您的朋友。=)
sam yi 2013年

2
@ Fr0zenFyr,只是因为RDBMS允许我们做某事,所以这样做不一定是一个好主意。没有什么迫使我们在表中声明主键或唯一键,但是除了少数例外,我们还是这样做。
Lennart

3
我认为对此主题的完整处理必须参考Codd的原始要求,即RDBMS必须具有系统性的方法来处理丢失的数据。在现实世界中,有些情况下会创建数据位置,但没有数据要放入其中。无论涉及数据库设计,应用程序编程或两者,Data Architect都必须对此做出一些响应。在满足此要求方面,SQL NULL不够完美,但总比没有好。
Walter Mitty

Answers:


229

我认为这个问题的措辞很差,因为措辞暗示您已经确定NULL是不好的。也许您的意思是“我们应该允许NULL吗?”

无论如何,这是我的看法:我认为NULL是一件好事。当仅仅因为“ NULL不好”或“ NULL很难”而开始防止NULL时,就开始构成数据。例如,如果您不知道我的出生日期怎么办?在知道之前,您将在列中输入什么?如果您像许多反NULL人士一样,则需要输入1900-01-01。现在,我将被安置在老年病房,可能会接到当地新闻台的电话,祝贺我长寿,询问我过这么长寿的秘密,等等。

如果可以在您可能不知道列值的地方输入行,我认为NULL比挑选一些任意的令牌值来表示它是未知的事实有意义得多-其他人将使用该值必须已经知道,进行逆向工程或四处询问以了解其含义。

但是有一个平衡-并非数据模型中的每一列都应该为空。表单上通常有可选字段,或者在创建行时不会收集到的某些信息。但这并不意味着您可以推迟填充所有数据。:-)

而且,在现实生活中,使用NULL的能力可能会受到关键要求的限制。例如,在医学领域,知道为什么值未知是一件生死攸关的事情。心率是否为NULL是因为没有脉搏,还是因为我们还没有测量脉搏?在这种情况下,我们可以将NULL放入心率列中,并在注释或其他列中包含NULL的原因吗?

不要害怕NULL,但是愿意学习或规定何时何地应该使用它们,何时不应该使用它们。


3
“一些任意的令牌值代表未知的事实”,这被称为前哨值
亚历山大

4
但是,是什么阻止您创建birth_date存储出生日期的单独表呢?如果生日未知,那就不要在出生日期中插入生日birth_date。空是灾难。
Eldar Agalarov

6
@EldarAgalarov这听起来像是特朗普的推理(“灾难”为何?为什么?如何?对于谁?您认为某事是“灾难”,但事实并非如此)。无论如何,出生日期只是一个例子。如果您的人员,成员或客户有15个可能为空的列,您是否要创建15个辅助表?如果您有50个怎么办?如果您的DW事实表有500个怎么办?维护以使大型坏的可怕NULL出入数据库的维护变得比您担心的任何“灾难”都要严重10倍...
Aaron Bertrand

3
@AaronBertrand,如果您的表有15个可能为空的列,则闻起来确实很糟糕^^并不是说大量的列本来就很糟糕,但是这可能表示设计错误或需要进行非规范化。但这会引起疑问。
programaths '18

2
@Wildcard因此,您从未见过有人存储1900-01-01以避免避免使用NULL日期/时间值吗?好吧。此外,NULL =未知,unknown =假。我不确定这可能会导致什么问题,而不是人们不是天生就知道这一点(就像他们不是天生就知道复杂的RDBMS中固有的很多事情一样)。再次挥舞着双手,说:“问题!灾难!” 并非如此。
亚伦·伯特兰

57

确定的原因是:

  • NULL不是值,因此没有内部数据类型。当原本依赖实际类型的代码也可能会收到未类型化的NULL时,Null则需要在各处进行特殊处理

  • NULL打破了两个值(熟悉的True或False)逻辑,并且需要一个三值逻辑。即使正确实施,这也要复杂得多,并且大多数DBA以及几乎所有非DBA肯定对此了解不多。结果,它肯定会在应用程序中引发许多细微的错误

  • 与实际值不同,任何特定NULL语义都留给应用程序

    诸如“不适用”,“未知”和“前哨”之类的语义很常见,还有其他语义。它们经常在同一数据库中甚至在同一关系中同时使用;并且当然是含混不清,难以区分和不兼容的含义。

  • “如何处理没有空值的缺失信息”中所述,它们对于关系数据库不是必需的。进一步的规范化是尝试消除NULL表的明显的第一步。

这并不意味着不应该允许NULL。它确实指出,有许多充分的理由在可行的情况下都不允许使用NULL。

值得一提的是,它认为必须通过更好的模式设计,更好的数据库引擎,甚至更好的数据库语言来进行艰苦的尝试,以使其能够更频繁地避免NULL。

Fabian Pascal在“ Nulls Nullified”中回应了许多论点。


3
您链接到“如何在没有空值的情况下处理丢失的信息”就很好地说明了为什么我们不能没有空值:在目前的主要RDBMS上,不可能以合理的方式实施一些建议。
杰克·道格拉斯

7
杰克:是的,但是“当前的实现无法做到”并不是维持现状的论点:-)
bignose 2011年

17
这是在说我们不应该因为飞机不完美而飞行吗?
亚伦·伯特兰

11
不,这是说供应商应该停止为空值使用借口,而这些空值在40年前可能是有效的,但在合理的保留期限内已经过期了很长时间。I / O时间不再是80ms的数量级。单CPU周期不再是微秒的数量级。内存限制不再是几个Meg的数量级。与40年前不同,现在不存在空值而工作所需的硬件速度和容量确实存在,而成本却并不高昂。他是说该继续前进了。
Erwin Smout,2012年

2
“ NULL混淆”链接已死。
jpmc26

32

我不同意,空值是数据库设计的基本要素。您也提到过,替代方法是增加已知值以表示缺失或未知。问题在于null被广泛误解,因此使用不当。

IIRC Codd建议通过使用两个空标记而不是一个空标记(“不存在但适用”和“不存在且不适用”)来改进当前null(意味着不存在/缺失)的实现。无法设想此人将如何改善关系设计。


2
我建议使用一组用户定义的不同种类的null,以及一组用户定义的多值逻辑:p
Jack Douglas

13
这些不是唯一的选择。您排除了标准化的替代方法:代替可能具有或可能没有值的列,而使用可能有或没有第一个表对应行的另一个表。存在或不存在一排的意义便要承担在表的意义,并有NULL或定点值等没有特殊的套管
bignose

7
NULL的存在不需要特殊的大小写或标记值。这些只是一些人决定如何处理NULL的症状。
亚伦·伯特兰

值得注意的是''与PostgreSQL上的null区别(尽管不是Oracle),因此它与null不同,因此为您提供了两个标记,对于数字列可以使用0。但是0的问题是它不适用于外键。
克里斯·特拉弗斯

13

首先,我要说我不是DBA,我是一个开发人员,我会根据需要维护和更新数据库。话虽这么说,但出于几个原因,我也有同样的问题。

  1. 空值会使开发更加困难且容易出错。
  2. 空值会使查询,存储过程和视图更加复杂且易于出错。
  3. 空值占用空间(基于固定列长度的?个字节,对于可变列长度的2个字节)。
  4. 空值通常会影响索引编制和数学运算。

我花了很长时间筛选整个互联网上的响应,评论,文章和建议。不用说,大多数信息与@AaronBertrand的回复大致相同。这就是为什么我觉得有必要回答这个问题的原因。

首先,我想为所有将来的读者设置一些简单的东西... NULL值表示未知数据,不是未使用的数据。如果您的雇员表具有终止日期字段。终止日期中的空值是因为它是当前未知的将来必填字段。每个在职或终止的员工在某个时候都会在该字段中添加一个日期。我认为这是可空字段的唯一原因。

话虽这么说,同一员工表最有可能保存某种身份验证数据。在企业环境中,通常会在数据库中列出员工的人力资源和会计信息,但并不总是拥有或需要身份验证详细信息。大多数答复会让您相信可以将这些字段为空,或者在某些情况下为它们创建一个帐户,但不要发送凭据。前者将使您的开发团队编写代码以检查NULL并进行相应的处理,而后者则构成了巨大的安全风险!系统中从未使用过的帐户只会增加黑客可能使用的访问点数量,而且它们会为未使用的内容占用宝贵的数据库空间。

给定以上信息,将使用的处理可空数据的最佳方法是允许可空值。这是可悲但真实的事情,您的开发人员会为此而讨厌您。第二种可为空的数据类型应放在相关表中(IE:帐户,凭据等),并具有一对一的关系。除非有必要,否则这允许用户不使用凭据而存在。这消除了额外的安全风险,宝贵的数据库空间,并提供了一个更加整洁的数据库。

下面是一个非常简单的表结构,它显示了必需的可空列和一对一关系。

未知的可为空和一对一关系

我知道自从几年前提出这个问题以来,我来晚了一些,但是希望这将有助于阐明这个问题以及如何最好地解决它。


2
我只是对其进行更改,以使TerminationDate雇员记录中没有任何内容,但是有一个表,TerminatedEmployee在雇员终止时应用程序将其移至该表(而不是复制)。显然,这在Account表中可以很好地工作,因为该TerminatedEmployee表上没有链接的帐户。如果您仍然需要电话号码,我将反转外键,以便employee和终止雇员表具有电话号码的id,而不是相反的方式。
Programster

2
我真的可以继续几天讨论为什么这会不好。冗余表,不良的SQL做法,使开发人员不得不在两个地方查找员工数据,报告问题,直接给不存在的员工的URI(已被移动)的问题,并且清单继续等等。对于总有一天会有价值的字段使用NULL完全没问题,这是另一回事,那就是字段永远不会被填充且永远不会被使用。进行这项工作的许多潜在问题和解决方法不值得在字段上检查NULL的小问题。
尼古拉斯·阿奎尔

1
我不同意。唯一多余的是终止日期的null字段可能永远不会填充。开发人员只需在适当的表中查找所需的数据,即可提高性能。如果由于某种原因,您想要终止的雇员和未终止的雇员,都可以通过加入来解决,但是90%的时间您的应用程序可能想要一个或另一个。我认为我指定的布局更好,因为不可能有雇员的离职日期,而仍然有一个帐户。
Programster

2
我不是说冗余数据,而是说冗余表。另外,对employee表的任何更改都必须细化到终止的表;这会使应用程序容易出错,并使开发人员的工作更加困难。此外,“终止日期”字段将填写给几乎所有人。创建第二个相同的表结构并四处移动数据既浪费又成问题。每次都不包含测试以确保表数据已移动并清除。从表中删除数据是不好的做法,即使只是移动它也是如此。如果您如此关注一个领域,那么……
Nicholas Aguirre

1
...这几乎总是会被及时填满,然后创建一个与员工保持1to1关系的终止表。作为一名DBA和一名开发人员,我整天都在处理各种数据库,但很高兴我还没有遇到您建议的结构。特别是从开发人员的角度来看,编写和错误检查所有内容将是一场噩梦,因为您不知道它来自哪个表。即使编写联接,返回到软件的数据也会有一个包含空数据的字段,这仍然需要您对其进行测试。
Nicholas Aguirre

13

除了NULL使开发人员困惑的所有问题外,NULL还有另一个非常严重的缺点:性能

从性能的角度来看,可空列是一个灾难。以整数算术为例。在没有NULL的理性世界中,使用SIMD指令对数据库引擎代码中的整数算术进行矢量化处理以“在每个CPU周期快于1行的速度进行几乎任何计算”是很容易的。但是,在引入NULL的那一刻,您需要处理NULL创建的所有特殊情况。现代CPU指令集(阅读:x86 / x64 / ARM和GPU逻辑)根本无法有效地做到这一点。

以除法为例。在非常高的层次上,这是您需要使用非null整数的逻辑:

if (b == 0)
  do something when dividing by error
else
  return a / b

使用NULL,这变得有些棘手。和b您一起将需要一个指标,如果b为null,则类似a。支票现在变成:

if (b_null_bit == NULL)
   return NULL
else if (b == 0) 
   do something when dividing by error
else if (a_null_bit == NULL)
   return NULL
else 
   return a / b

NULL算法在现代CPU上的运行速度比非null算法要慢得多(约为2-3倍)。

当您引入SIMD时,情况会变得更糟。借助SIMD,现代的Intel CPU可以在一条指令中执行4 x 32位整数除法,如下所示:

x_vector = a_vector / b_vector
if (fetestexception(FE_DIVBYZERO))
   do something when dividing by zero
return x_vector;

现在,也有办法在SIMD区域中处理NULL,但这需要使用更多的向量和CPU寄存器并进行一些巧妙的位屏蔽。即使有一些好的技巧,即使是相对简单的表达式,NULL整数算术的性能损失也会慢5至10倍。

像上面这样的东西对于聚合以及在某种程度上对于联接也是成立的。

换句话说:SQL中NULL的存在是数据库理论与现代计算机的实际设计之间的阻抗不匹配。NULL有一个很好的理由使开发人员感到困惑-因为在大多数理智的编程语言中,整数不能为NULL-但这不是计算机的工作方式。


10

有趣的问题。

我似乎只能想到的是,作为应用程序开发人员,您不必测试NULL和可能不存在的数据值(例如,字符串的空字符串)。

比这更复杂。Null有许多不同的含义,并且在许多列中不允许使用null的一个重要原因是,当该列为null时,则意味着一件事而且只有一件事(即它没有出现在外部联接中)。此外,它还允许您设置数据输入的最低标准,这确实很有帮助。

但是,对于日期,日期时间和时间,该怎么办(SQL Server 2008)?您必须使用一些历史性的或触底反弹的日期。

这说明立即存在null的问题,即存储在表中的值可能表示“此值不适用”或“我们不知道”。对于字符串,空字符串可以用作“这不适用”,但是对于日期和时间,则没有这样的约定,因为没有常规意义上的有效值。通常情况下,您将使用NULL卡住。

有一些方法可以解决此问题(通过添加更多关系和联接),但是这些方法与在数据库中具有NULL的情况下存在完全相同的语义清晰度问题。对于这些数据库,我不会为此担心。真的,您对此无能为力。

编辑:一个领域是空值不可缺少的是外键。在这里,它们通常只有一个含义,与外部连接含义中的null相同。当然,这是一个例外。


10

Wikipedia上有关SQL Null的文章对 NULL值进行了一些有趣的评论,并且作为与数据库无关的答案,只要您知道特定RDBMS具有NULL值的潜在影响,它们在您的设计中就可以接受。如果不是,则无法将列指定为可为空。

只要知道您的RDBMS如何在SELECT操作(例如数学)以及索引中处理它们即可。


-12

哇,正确的答案“在不必要的时候不要允许NULL,因为它们会降低性能”是不知何故的最后答案。我会投票赞成并加以阐述。当RDBMS允许非稀疏列为NULL时,该列将添加到位图,该位图跟踪每个单独行的值是否为NULL。因此,通过向表中的所有列均不允许为NULL的列添加NULL功能,可以增加保存表所需的存储空间。此外,您还要求RDBMS读取和写入位图,从而降低所有操作的性能。

此外,在许多情况下,允许NULL将破坏3NF。尽管我不像我的许多同事那样坚决支持3NF,但请考虑以下情形:

在“人员”表中,有一列称为DateOfDeath,该列可以为空。如果一个人死亡,将用其DateOfDeath填充,否则将为NULL。还有一个称为IsAlive的不可为空的位列。如果此人还活着,则此列设置为1;如果该人已死,则此列设置为0。绝大多数存储过程都使用IsAlive列,它们仅在一个人还活着的时候在乎,而不在乎其DateOfDeath。

但是,IsAlive列破坏了数据库规范化,因为它完全可以从DateOfDeath派生。但是,由于IsAlive硬连接到大多数SP中,因此直接的解决方案是使DateOfDeath不可为空,并在此人还活着的情况下为该列分配默认值。然后,可以重写几个使用DateOfDeath的SP,以检查IsAlive列,并且仅在此人还活着时才尊重DateOfDeath。同样,由于大多数SP仅关心IsAlive(有点),而不关心DateOfDeath(日期),因此使用此模式可以大大提高访问速度。

查找所有模式中没有NULL的可空列的有用的T-SQL脚本是:

select 'IF NOT EXISTS (SELECT 1 FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' WHERE ' + QUOTENAME(c.name) + ' IS NULL)
    AND (SELECT COUNT(*) FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ') > 1 PRINT ''' + s.name + '.' + t.name + '.' + REPLACE(c.name, '''', '''''') + ''''
    from sys.columns c
    inner join sys.tables t ON c.object_id = t.object_id
    inner join sys.schemas s ON s.schema_id = t.schema_id
    where c.is_nullable = 1 AND c.is_computed = 0
    order by s.name, t.name, c.name;

如果在生产数据库的副本上运行此代码,则可以找到开发人员标记为允许NULL的列,而这些列实际上没有NULL。其中的绝大多数可以标记为NOT NULL,从而提高性能并减少存储空间。

可能无法消除所有表中的所有NULL,并且仍然具有简洁的设计,但是在消除尽可能多的NULL方面具有相当大的优势。优化程序使用此信息可以更快地工作,并且如果您可以消除表中的所有NULL,则可以重新获得大量的存储空间。

我知道性能并不是DBA会考虑的全部问题,但是您只能在解决方案中投入有限的内存和处理器功能,因此您必须开始考虑逻辑和物理设计。

另请注意,这仅适用于真正的RDBMS,并且我将答案的技术部分基于SQL Server。列出的T-SQL也可以从SQL Server中找到没有空值的可空列。


1
评论不作进一步讨论;此对话已转移至聊天
保罗·怀特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.