我记得读过一篇有关数据库设计的文章,并且我还记得它说您应该具有NOT NULL的字段属性。我不记得为什么会这样。
我似乎只能想到的是,作为应用程序开发人员,您无需测试NULL 和可能不存在的数据值(例如,字符串的空字符串)。
但是,对于日期,日期时间和时间,该怎么办(SQL Server 2008)?您必须使用一些历史性的或触底反弹的日期。
有什么想法吗?
我记得读过一篇有关数据库设计的文章,并且我还记得它说您应该具有NOT NULL的字段属性。我不记得为什么会这样。
我似乎只能想到的是,作为应用程序开发人员,您无需测试NULL 和可能不存在的数据值(例如,字符串的空字符串)。
但是,对于日期,日期时间和时间,该怎么办(SQL Server 2008)?您必须使用一些历史性的或触底反弹的日期。
有什么想法吗?
Answers:
我认为这个问题的措辞很差,因为措辞暗示您已经确定NULL是不好的。也许您的意思是“我们应该允许NULL吗?”
无论如何,这是我的看法:我认为NULL是一件好事。当仅仅因为“ NULL不好”或“ NULL很难”而开始防止NULL时,就开始构成数据。例如,如果您不知道我的出生日期怎么办?在知道之前,您将在列中输入什么?如果您像许多反NULL人士一样,则需要输入1900-01-01。现在,我将被安置在老年病房,可能会接到当地新闻台的电话,祝贺我长寿,询问我过这么长寿的秘密,等等。
如果可以在您可能不知道列值的地方输入行,我认为NULL比挑选一些任意的令牌值来表示它是未知的事实有意义得多-其他人将使用该值必须已经知道,进行逆向工程或四处询问以了解其含义。
但是有一个平衡-并非数据模型中的每一列都应该为空。表单上通常有可选字段,或者在创建行时不会收集到的某些信息。但这并不意味着您可以推迟填充所有数据。:-)
而且,在现实生活中,使用NULL的能力可能会受到关键要求的限制。例如,在医学领域,知道为什么值未知是一件生死攸关的事情。心率是否为NULL是因为没有脉搏,还是因为我们还没有测量脉搏?在这种情况下,我们可以将NULL放入心率列中,并在注释或其他列中包含NULL的原因吗?
不要害怕NULL,但是愿意学习或规定何时何地应该使用它们,何时不应该使用它们。
birth_date
存储出生日期的单独表呢?如果生日未知,那就不要在出生日期中插入生日birth_date
。空是灾难。
1900-01-01
以避免避免使用NULL日期/时间值吗?好吧。此外,NULL =未知,unknown =假。我不确定这可能会导致什么问题,而不是人们不是天生就知道这一点(就像他们不是天生就知道复杂的RDBMS中固有的很多事情一样)。再次挥舞着双手,说:“问题!灾难!” 并非如此。
确定的原因是:
NULL不是值,因此没有内部数据类型。当原本依赖实际类型的代码也可能会收到未类型化的NULL时,Null则需要在各处进行特殊处理。
NULL打破了两个值(熟悉的True或False)逻辑,并且需要一个三值逻辑。即使正确实施,这也要复杂得多,并且大多数DBA以及几乎所有非DBA肯定对此了解不多。结果,它肯定会在应用程序中引发许多细微的错误。
与实际值不同,任何特定NULL的语义都留给应用程序。
诸如“不适用”,“未知”和“前哨”之类的语义很常见,还有其他语义。它们经常在同一数据库中甚至在同一关系中同时使用;并且当然是含混不清,难以区分和不兼容的含义。
如“如何处理没有空值的缺失信息”中所述,它们对于关系数据库不是必需的。进一步的规范化是尝试消除NULL表的明显的第一步。
这并不意味着不应该允许NULL。它确实指出,有许多充分的理由在可行的情况下都不允许使用NULL。
值得一提的是,它认为必须通过更好的模式设计,更好的数据库引擎,甚至更好的数据库语言来进行艰苦的尝试,以使其能够更频繁地避免NULL。
Fabian Pascal在“ Nulls Nullified”中回应了许多论点。
我不同意,空值是数据库设计的基本要素。您也提到过,替代方法是增加已知值以表示缺失或未知。问题在于null被广泛误解,因此使用不当。
IIRC Codd建议通过使用两个空标记而不是一个空标记(“不存在但适用”和“不存在且不适用”)来改进当前null(意味着不存在/缺失)的实现。无法设想此人将如何改善关系设计。
null
,以及一组用户定义的多值逻辑:p
首先,我要说我不是DBA,我是一个开发人员,我会根据需要维护和更新数据库。话虽这么说,但出于几个原因,我也有同样的问题。
- 空值会使开发更加困难且容易出错。
- 空值会使查询,存储过程和视图更加复杂且易于出错。
- 空值占用空间(基于固定列长度的?个字节,对于可变列长度的2个字节)。
- 空值通常会影响索引编制和数学运算。
我花了很长时间筛选整个互联网上的响应,评论,文章和建议。不用说,大多数信息与@AaronBertrand的回复大致相同。这就是为什么我觉得有必要回答这个问题的原因。
首先,我想为所有将来的读者设置一些简单的东西... NULL值表示未知数据,不是未使用的数据。如果您的雇员表具有终止日期字段。终止日期中的空值是因为它是当前未知的将来必填字段。每个在职或终止的员工在某个时候都会在该字段中添加一个日期。我认为这是可空字段的唯一原因。
话虽这么说,同一员工表最有可能保存某种身份验证数据。在企业环境中,通常会在数据库中列出员工的人力资源和会计信息,但并不总是拥有或需要身份验证详细信息。大多数答复会让您相信可以将这些字段为空,或者在某些情况下为它们创建一个帐户,但不要发送凭据。前者将使您的开发团队编写代码以检查NULL并进行相应的处理,而后者则构成了巨大的安全风险!系统中从未使用过的帐户只会增加黑客可能使用的访问点数量,而且它们会为未使用的内容占用宝贵的数据库空间。
给定以上信息,将使用的处理可空数据的最佳方法是允许可空值。这是可悲但真实的事情,您的开发人员会为此而讨厌您。第二种可为空的数据类型应放在相关表中(IE:帐户,凭据等),并具有一对一的关系。除非有必要,否则这允许用户不使用凭据而存在。这消除了额外的安全风险,宝贵的数据库空间,并提供了一个更加整洁的数据库。
下面是一个非常简单的表结构,它显示了必需的可空列和一对一关系。
我知道自从几年前提出这个问题以来,我来晚了一些,但是希望这将有助于阐明这个问题以及如何最好地解决它。
TerminationDate
雇员记录中没有任何内容,但是有一个表,TerminatedEmployee
在雇员终止时应用程序将其移至该表(而不是复制)。显然,这在Account表中可以很好地工作,因为该TerminatedEmployee
表上没有链接的帐户。如果您仍然需要电话号码,我将反转外键,以便employee和终止雇员表具有电话号码的id,而不是相反的方式。
除了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-但这不是计算机的工作方式。
有趣的问题。
我似乎只能想到的是,作为应用程序开发人员,您不必测试NULL和可能不存在的数据值(例如,字符串的空字符串)。
比这更复杂。Null有许多不同的含义,并且在许多列中不允许使用null的一个重要原因是,当该列为null时,则意味着一件事而且只有一件事(即它没有出现在外部联接中)。此外,它还允许您设置数据输入的最低标准,这确实很有帮助。
但是,对于日期,日期时间和时间,该怎么办(SQL Server 2008)?您必须使用一些历史性的或触底反弹的日期。
这说明立即存在null的问题,即存储在表中的值可能表示“此值不适用”或“我们不知道”。对于字符串,空字符串可以用作“这不适用”,但是对于日期和时间,则没有这样的约定,因为没有常规意义上的有效值。通常情况下,您将使用NULL卡住。
有一些方法可以解决此问题(通过添加更多关系和联接),但是这些方法与在数据库中具有NULL的情况下存在完全相同的语义清晰度问题。对于这些数据库,我不会为此担心。真的,您对此无能为力。
编辑:一个领域是空值是不可缺少的是外键。在这里,它们通常只有一个含义,与外部连接含义中的null相同。当然,这是一个例外。
Wikipedia上有关SQL Null的文章对 NULL值进行了一些有趣的评论,并且作为与数据库无关的答案,只要您知道特定RDBMS具有NULL值的潜在影响,它们在您的设计中就可以接受。如果不是,则无法将列指定为可为空。
只要知道您的RDBMS如何在SELECT操作(例如数学)以及索引中处理它们即可。
哇,正确的答案“在不必要的时候不要允许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中找到没有空值的可空列。