我记得读过一篇关于数据库设计的文章,我还记得它说你应该有 NOT NULL 的字段属性。我不记得为什么会这样。
我所能想到的似乎是,作为应用程序开发人员,您不必测试 NULL和可能不存在的数据值(例如,字符串的空字符串)。
但是对于日期、日期时间和时间(SQL Server 2008),您会怎么做?你必须使用一些历史性的或触底的日期。
对此有什么想法吗?
我记得读过一篇关于数据库设计的文章,我还记得它说你应该有 NOT NULL 的字段属性。我不记得为什么会这样。
我所能想到的似乎是,作为应用程序开发人员,您不必测试 NULL和可能不存在的数据值(例如,字符串的空字符串)。
但是对于日期、日期时间和时间(SQL Server 2008),您会怎么做?你必须使用一些历史性的或触底的日期。
对此有什么想法吗?
我认为这个问题措辞不当,因为措辞暗示您已经决定 NULL 不好。也许您的意思是“我们应该允许 NULL 吗?”
无论如何,这是我的看法:我认为 NULL 是一件好事。当您仅仅因为“NULL 不好”或“NULL 很难”而开始阻止 NULL 时,您就开始编造数据。例如,如果你不知道我的出生日期怎么办?在你知道之前你会在专栏里放什么?如果您像很多反 NULL 的人一样,您将输入 1900-01-01。现在我将被安置在老年病房,可能会接到当地新闻台的电话,祝贺我长寿,问我长寿的秘诀等等。
如果可以在可能不知道列值的情况下输入一行,我认为 NULL 比选择一些任意标记值来表示它是未知的事实更有意义 - 其他人将必须已经知道,逆向工程,或四处询问以弄清楚这意味着什么。
但是,有一个平衡点 - 并非数据模型中的每一列都应该可以为空。表单上通常有可选字段,或者在创建行时不会收集的信息片段。但这并不意味着您可以推迟填充所有数据。:-)
此外,使用 NULL 的能力可能会受到现实生活中的关键要求的限制。例如,在医学领域,知道为什么一个值是未知的可能是生死攸关的事情。心率为 NULL 是因为没有脉搏,还是因为我们还没有测量?在这种情况下,我们是否可以将 NULL 放在心率列中,并在注释或不同的列中添加 NULL-because 原因?
不要害怕 NULL,但要愿意学习或指示何时何地应该使用它们,以及何时何地不应该使用它们。
确定的原因是:
NULL 不是一个值,因此没有内在数据类型。当依赖于实际类型的代码也可能接收到无类型的 NULL 时,空值需要在所有地方进行特殊处理。
NULL 打破了二值(熟悉的 True 或 False)逻辑,并且需要三值逻辑。即使正确实现,这也要复杂得多,而且大多数 DBA 和几乎所有非 DBA 都很难理解。因此,它肯定会在应用程序中引发许多微妙的错误。
与实际值不同,任何特定 NULL的语义都留给应用程序。
“不适用”、“未知”和“哨兵”等语义很常见,还有其他语义。它们经常在同一个数据库中同时使用,甚至在同一个关系中;当然是含糊不清、无法区分和不相容的含义。
正如“如何在没有空值的情况下处理丢失的信息”中所述,它们对于关系数据库来说不是必需的。进一步规范化显然是尝试删除 NULL 表的第一步。
这并不意味着永远不应该允许 NULL。它确实认为有很多充分的理由在可行的情况下禁止 NULL。
重要的是,它主张非常努力地尝试——通过更好的模式设计、更好的数据库引擎,甚至更好的数据库语言——以使更频繁地避免 NULL 变得可行。
Fabian Pascal 在“Nulls Nullified”中回应了许多论点。
我不同意,空值是数据库设计的基本要素。正如您也提到的那样,另一种选择是使用已知值的扩散来表示缺失或未知。问题在于 null 被如此广泛地误解并因此被不当使用。
IIRC,Codd 建议可以通过使用两个空标记而不是一个“不存在但适用”和“不存在且不适用”来改进 null 的当前实现(意味着不存在/缺失)。无法想象个人将如何改进关系设计。
首先让我说我不是 DBA,我是一名开发人员,我根据我们的需要维护和更新我们的数据库。话虽如此,出于几个原因,我有同样的问题。
我花了很长时间筛选互联网上的大量回复、评论、文章和建议。不用说,大部分信息与@AaronBertrand 的回复大致相同。这就是为什么我觉得有必要回答这个问题。
首先,我想为所有未来的读者直接设置一些东西...... NULL 值代表未知数据而不是未使用的数据。如果您有一个包含终止日期字段的员工表。终止日期中的空值是因为它是当前未知的未来必填字段。每个员工,无论是活跃的还是终止的,都会在某个时候在该字段中添加一个日期。在我看来,这是 Nullable 字段的唯一原因。
话虽如此,同一个员工表很可能会保存某种身份验证数据。在企业环境中,员工通常会被列在 HR 和会计数据库中,但并不总是拥有或需要身份验证详细信息。大多数回复会让您相信可以将这些字段清空,或者在某些情况下为它们创建一个帐户,但从不向他们发送凭据。前者会导致您的开发团队编写代码来检查 NULL 并相应地处理它们,而后者会带来巨大的安全风险!系统中从未使用过的帐户只会增加黑客可能的访问点数量,而且它们会占用宝贵的数据库空间来存放从未使用过的东西。
鉴于上述信息,处理将要使用的可空数据的最佳方法是允许可空值。这是可悲但真实的,您的开发人员会因此而讨厌您。第二种可以为空的数据应该放在一个相关的表中(IE:Account、Credentials 等)并且具有一对一的关系。这允许用户在没有凭据的情况下存在,除非需要他们。这消除了额外的安全风险、宝贵的数据库空间,并提供了一个更干净的数据库。
下面是一个非常简单的表结构,显示了所需的可为空列和一对一关系。
我知道自从几年前提出这个问题以来,我参加聚会有点晚了,但希望这将有助于阐明这个问题以及如何最好地处理它。
除了 NULL 让开发人员感到困惑的所有问题之外,NULL 还有另一个非常严重的缺点:性能
从性能的角度来看,可空列是一场灾难。以整数算术为例。在没有 NULL 的理智世界中,使用 SIMD 指令在数据库引擎代码中对整数算术进行矢量化是“容易的”,以比每个 CPU 周期 1 行更快的速度执行几乎任何计算。但是,在您引入 NULL 的那一刻,您需要处理 NULL 创建的所有特殊情况。现代 CPU 指令集(阅读:x86/x64/ARM 和 GPU 逻辑)根本无法有效地执行此操作。
以除法为例。在非常高的级别上,这是您需要使用非空整数的逻辑:
使用 NULL,这变得有点棘手。与
b
您一起将需要一个指示符 ifb
为 null 并且类似地为a
. 支票现在变成:NULL 算术在现代 CPU 上运行比非空算术慢得多(大约 2-3 倍)。
当您引入 SIMD 时,情况会变得更糟。使用 SIMD,现代 Intel CPU 可以在一条指令中执行 4 x 32 位整数除法,如下所示:
现在,在 SIMD 领域也有处理 NULL 的方法,但这需要使用更多的向量和 CPU 寄存器并进行一些巧妙的位掩码。即使有很好的技巧,对于相对简单的表达式,NULL 整数运算的性能损失也会慢 5-10 倍。
像上面这样的东西适用于聚合,在某种程度上也适用于连接。
换句话说:SQL中NULL的存在是数据库理论与现代计算机实际设计之间的阻抗不匹配。NULL 让开发人员感到困惑是有一个很好的理由——因为在大多数理智的编程语言中整数不能为 NULL——这不是计算机的工作方式。
Wikipedia 关于 SQL Null 的文章对NULL 值有一些有趣的评论,并且作为与数据库无关的答案,只要您意识到 NULL 值对您的特定 RDBMS 的潜在影响,它们在您的设计中是可以接受的。如果不是,您将无法将列指定为可为空的。
请注意您的 RDBMS 如何在 SELECT 操作(例如数学)以及索引中处理它们。
有趣的问题。
比这更复杂。Null 有许多不同的含义,不允许在许多列中使用 null 的一个非常重要的原因是,当该列为 null 时,这意味着只有一件事(即它没有出现在外部连接中)。此外,它允许您设置数据输入的最低标准,这非常有用。
这立即说明了空值的问题,即存储在表中的值可能意味着“此值不适用”或“我们不知道”。对于字符串,空字符串可以用作“这不适用”,但对于日期和时间,没有这样的约定,因为没有有效的值通常意味着这个。通常,您将在使用 NULL 时遇到困难。
有一些方法可以解决这个问题(通过添加更多的关系和加入),但是这些方法会带来与数据库中存在 NULL 完全相同的语义清晰度问题。对于这些数据库,我不会担心这一点。你真的无能为力。
编辑:NULL 必不可少的一个领域是外键。在这里,它们通常只有一个含义,与外连接含义中的 null 相同。这当然是问题的一个例外。
哇,正确的答案“当你不需要时不要允许 NULL,因为它们会降低性能”不知何故是最后一个评分的答案。我会赞成并详细说明。当 RDBMS 允许非稀疏列为 NULL 时,该列被添加到位图中,该位图跟踪每个单独行的值是否为 NULL。因此,通过向所有列都不允许 NULL 的表中的列添加 NULL 能力,您正在增加保存表所需的存储空间。此外,您需要 RDBMS 读取和写入位图,从而降低所有操作的性能。
此外,在许多情况下,允许 NULL 将破坏 3NF。虽然我不像我的许多同事那样坚持 3NF,但请考虑以下情况:
在 Person 表中有一个名为 DateOfDeath 的列,它可以为空。如果一个人已经死亡,它将用他们的 DateOfDeath 填充,否则它将为 NULL。还有一个称为 IsAlive 的不可为空的位列。如果此人还活着,则此列设置为 1,如果此人已死,则此列设置为 0。绝大多数存储过程使用 IsAlive 列,它们只关心一个人是否还活着,而不关心他们的 DateOfDeath。
但是,IsAlive 列破坏了数据库规范化,因为它完全可以从 DateOfDeath 派生。但由于 IsAlive 已硬连接到大多数 SP,直接的解决方案是使 DateOfDeath 不可为空,并在此人还活着的情况下为该列分配一个默认值。然后可以重写使用 DateOfDeath 的少数 SP 以检查 IsAlive 列,并且仅在此人不活着时才尊重 DateOfDeath。同样,由于大多数 SP 只关心 IsAlive(一点点)而不关心 DateOfDeath(一个日期),因此使用这种模式可以大大加快访问速度。
一个有用的 T-SQL 脚本,用于在所有模式中查找没有 NULL 的可空列:
如果您在生产数据库的副本上运行此程序,您会发现开发人员标记为允许 NULL 的列实际上没有 NULL。其中绝大多数可以标记为 NOT NULL,从而提高性能并降低存储空间。
可能不可能消除所有表中的所有 NULL 并且仍然具有简洁的设计,但是消除尽可能多的 NULL 具有相当大的优势。优化器使用此信息工作得更快,如果您可以消除表中的所有 NULL,您可以重新获得大量存储空间。
我知道性能并不是 DBA 考虑太多的事情,但是您只能在解决方案中投入有限的内存和处理器能力,在某个时候您将不得不开始考虑逻辑和物理设计.
另请注意,这仅适用于真正的 RDBMS,我的答案的技术部分基于 SQL Server。列出的用于查找不带空值的可空列的 T-SQL 也来自 SQL Server。