27

我正在设计一个数据库模式,我想知道我应该使用什么标准来决定每一列是否应该是nullable

我是否应该仅将那些绝对必须填写一行才能对我的应用程序有意义的列标记为 NOT NULL?

或者我应该标记我打算永远不会为空的所有列?

少量与大量 NOT NULL 列的性能影响是什么?

我假设很多 NOT NULL 列会减慢插入速度,但实际上可能会加快选择速度,因为查询执行计划生成器有更多关于列的信息。

比我知识渊博的人能给我介绍一下吗?

4

15 回答 15

30

老实说,我一直认为 NOT NULL 应该是默认值。NULL 是一种奇怪的特殊情况,无论何时使用它都应该为它做一个案例。另外,将一列从 NOT NULL 更改为可为空的比另一种方式要容易得多。

于 2009-03-18T00:06:56.880 回答
15

没有显着的性能后果。甚至不要考虑将其视为一个问题。这样做是一个巨大的早期优化反模式。

“我是否应该只将那些绝对必须填写一行才能对我的应用程序有意义的列标记为 NOT NULL?”

是的。就这么简单。使用没有任何 NULL 值的 NULLable 列比需要 NULL 并不得不伪造它要好得多。无论如何,最好在您的业务规则中过滤掉任何模棱两可的情况。


编辑:

对于可空字段,还有一个我认为最终最引人注目的论点,那就是用例论点。我们都受制于需要某些字段值的数据输入表单;我们都放弃了对必填字段没有合理值的表单。最终,应用程序、表单和数据库设计只有在反映用户需求的情况下才是可防御的;很明显,有很多很多的数据库列,用户不能为其提供任何价值——有时在业务流程的给定点上,有时在任何时候。

于 2009-03-17T23:37:59.493 回答
12

在 NOT NULL 一侧出错。在某些时候,您必须决定 NULL 在您的应用程序中“意味着”什么——很可能,对于不同的列来说,这将是不同的事情。一些常见情况是“未指定”、“未知”、“不适用”、“尚未发生”等。您将知道何时需要其中一个值,然后您可以适当地允许 NULLable 列和编码围绕它的逻辑。

允许随机事物为 NULL 迟早总是一个噩梦 IME。谨慎而谨慎地使用 NULL - 并了解它在您的逻辑中的含义。

编辑:似乎有一个想法,我一直在争论NO空列。这是荒谬的。NULL很有用,但仅限于预期的地方。

Le Dorfier 的 DateOfDeath 例子就是一个很好的例子。NULL DateOfDeath 表示“尚未发生”。现在,我可以编写一个视图 LivingPersons WHERE DateOfDeath IS NULL

但是,NULL OrderDate 是什么意思?还没下单?即使 Order 表中有记录?NULL地址怎么样?在你让 NULL 成为一个值之前,这些是你应该考虑的想法。

回到 DateOfDeath - 对人员的查询WHERE DateOfDeath > '1/1/1999'不会返回 NULL 记录 - 即使我们从逻辑上知道他们必须在 1999 年之后死亡。那是你要的吗?如果没有,那么您最好包含OR DateOfDeath IS NULL在该查询中。如果您允许所有列都为 NULL,则每次编写查询时都必须考虑这一点。IME,对于 10% 左右的列来说,当它们为 NULL 时实际上具有合法意义的列的心理负担太大了。

于 2009-03-18T00:21:34.553 回答
10

我发现将列标记为 NOT NULL 通常是一个好主意,除非您对列中的 NULL 有有用的含义。否则,当你意识到你不想要它时,你可能会意外地在那里找到 NULL,并且更改更难。

于 2009-03-17T23:44:09.197 回答
10

我尽量避免在数据库中使用 NULL。这意味着字符字段始终不为空。数字字段也是如此,尤其是任何代表金钱或类似的东西(股票、单位等)。

我有两个例外:

  1. 日期可能未知的日期(例如 DivorcedOn)
  2. 可选的外键关系(MarriedToPersonId)。虽然有时我在外键表中使用了“空白”行并强制建立关系(例如 JobDescriptionCode)

我有时也会对“未知”/“未设置”使用显式位字段(例如 JobDescriptionCode 和 IsEmployeed)。

我有几个核心原因:

  1. NULL 总是会导致数值字段出现问题。总是。总是。总是。无论您在某个时候多么小心,都选择 X + Y,因为 Total 将会发生并且它将返回 NULL。
  2. NULL 很容易导致字符串字段出现问题,通常是地址字段(例如,从地址中选择 AddrLine1 + AddrLine2)。
  3. 防止业务逻辑层中的 NULL 是一种乏味的浪费工作……只是不要让它们进入数据库,您可以节省 100 行代码。

我的首选默认值:

  • 字符串 -> "",也就是一个空字符串
  • 数字 -> 0
  • 日期 -> 今天或 NULL(见异常 #1)
  • 位 -> 假
于 2009-03-18T00:41:12.977 回答
7

您可能会发现 Chris Date 的Database In Depth是解决这类问题的有用资源。你可以在这次采访中了解他的想法,他说:

所以是的,我确实认为 SQL 很糟糕。但是你明确地问它的主要缺陷是什么。好吧,这里有几个:

  • 重复行
  • 空值
  • 从左到右的列排序
  • 未命名的列和重复的列名
  • 未能正确支持“=”
  • 指针
  • 高冗余

以我自己的经验,几乎所有“计划的空值”都可以用具有基表外键的子表更好地表示。参与子表是可选的,这就是实际区分 null/not null 的地方。

这很好地映射到将关系解释为一阶逻辑命题。这也只是常识。当一个人不知道 Bob 的地址时,是否会在自己的通讯录中写入:

Bob. ____

或者,在有他的实际地址之前,人们是否只是避免为 Bob 填写地址卡?

编辑:Date 的论点出现在 Database In Depth 第 53-55 页的“为什么禁止 Nulls ”部分标题下。

于 2009-03-18T00:56:22.737 回答
4

除非我看到其他原因,否则我倾向于 NOT NULL —— 就像其他人所说的,不管你喜不喜欢,NULL 是一种奇怪的特殊情况。

关于 NULL,我最喜欢的一项是:

SELECT F1 FROM T WHERE F2 <> 'OK'

...其中(至少在 DB2 中)不会包含 f2 为空的任何行——因为在关系术语中, (NULL <> 'OK') IS NULL。但是您的意图是返回所有不正常的行。您需要一个额外的 OR 谓词,或者改写 F2 DISTINCT FROM 'OK' (这首先是特殊情况编码)。

IMO,NULL 只是那些需要艺术和科学一样多的程序员工具之一,例如指针算术或运算符重载。

Joe Celko 在 SQL For Smarties 中写到了这一点——在应用程序中使用 NULL 的陷阱是它的含义是未定义的。它可能意味着未知、未初始化、不完整、不适用——或者就像上面那个愚蠢的例子,它意味着 OK 还是 not-OK?

于 2009-03-18T01:31:23.667 回答
4

感谢所有伟大的答案,伙计们。您给了我很多思考,并帮助我形成了自己的观点/策略,归结为:

如果该列中的空值对您的应用程序具有特定意义,则允许空值。

null 的几个常见含义:

  • 任何直接来自用户的东西
    • 这里的 null 表示“用户没有进入”
    • 对于这些列,最好允许空值,否则无论如何您只会得到asdasd@asd.com类型的输入。
  • “0 或 1”关系的外键
    • null 表示“没有相关行”
    • 所以允许这些列的空值
    • 这个是有争议的,但这是我的观点。

一般来说,如果你想不出一个列中 null 的有用含义,它应该是NOT NULL. 您以后可以随时将其更改为可为空的。

我最终得到的那种事情的例子:

create table SalesOrderLine (
    Id int identity primary key,
    -- a line must have exactly one header:
    IdHeader int not null foreign key references SalesOrderHeader, 
    LineNumber int not null, -- a line must have a line number
    IdItem int not null, -- cannot have null item
    Quantity decimal not null, -- maybe could sell 0, but not null
    UnitPrice decimal not null, -- price can be 0, but not null
    -- a null delivery address means not for delivery:
    IdDeliveryAddress int foreign key references Address, 
    Comment varchar(100), -- null means user skipped it
    Cancelled bit not null default (0) -- true boolean, not three-state!
    Delivered datetime, -- null means not yet delivered
    Logged datetime not null default (GetDate()) -- must be filled out
)
于 2009-03-18T23:18:50.217 回答
2

我倾向于同意多菲尔的观点。

在您的应用程序中认真对待接收数据库 NULL 值并将它们视为空值时的灵活性,并且您给自己很大的灵活性来让 NULL 插入您未指定的值。

可能在很多情况下,您需要一些非常严格的数据完整性(和/或禁止 NULL 字段的强烈速度优化),但我认为这些担忧与确保每个字段都有默认值所需的额外努力有所缓和和/或设置为合理的值。

于 2009-03-17T23:43:33.880 回答
2

坚持在所有事情上都使用 NOT NULL,直到有人对此感到痛苦。然后尽可能不情愿地将其一次删除一列。尽可能避免数据库中的空值,只要可以。

于 2009-03-18T00:37:10.553 回答
2

就我个人而言,我认为您应该根据它们包含的数据类型、是否确实要求数据始终存在以及数据在输入时是否始终已知,将列标记为 Null 或不为 null。当用户没有数据时将列标记为非空将强制然后组成使您的所有数据无用的数据(这就是您最终得到垃圾数据的方式,例如包含“thisissilly@Ihatethisaplication.com”的电子邮件字段”)。不要求流程正常工作必须有的东西(比如显示客户下订单的关键字段)同样愚蠢。Null Vice not null 本质上是一个数据完整性问题,做对保持数据可用最有意义的事情。

于 2009-03-18T18:23:08.620 回答
1

如果您可以长期思考,那么在列中包含 NULL 会影响您设计查询的方式。无论您是使用 CASE 语句、COALESCE,还是必须显式测试 NULL 值,都可以为您做出决定。

从性能的角度来看,不必担心 NULLS 会更快。从设计的角度来看,使用 NULL 是一种了解项目从未被填写过的简单方法。有用的示例包括“UpdatedDateTime”列。NULL 表示项目从未更新过。

我个人在大多数情况下都允许 NULL。

于 2009-03-17T23:49:05.973 回答
1

少量与大量 NOT NULL 列的性能影响是什么?

这可能是显而易见的,但是,当一列可以为空时,每条记录将需要 1 个额外的存储位。因此,一个BIT列在可为空时将多消耗 100% 的存储空间,而UNIQUEIDENTIFIER在可为空时仅多消耗 0.8% 的存储空间。

在病态的情况下,如果您的数据库有一个由单个 BIT 列组成的表,则使该列可为空的决定会使数据库的性能降低一半。但是,在绝大多数现实世界场景下,可空性不会对性能产生可衡量的影响。

于 2009-03-18T01:32:35.117 回答
0

使用“Not Null”或“Null”应主要由您的特定持久性要求驱动。

值为 Nullable 意味着有两个或三个状态(具有位字段的三个状态)

例如; 如果我有一个名为“IsApproved”的位字段,并且该值设置在插入之后的阶段。然后是三种状态:

  1. 'IsApproved' 未回答
  2. 'IsApproved' 已获批准
  3. 'IsApproved' 未获批准

因此,如果一个字段可以合法地被视为未回答并且没有合适的默认值。这些字段应该被认为是可以为空的

于 2009-03-22T22:12:51.927 回答
-1

任何可为空的列都违反了第三范式。

但是,这不是答案。

也许是这样:数据库中有两种类型的列——一种保存数据的结构,一种保存数据的内容。键是结构,用户可输入的字段是数据。其他事情 - 嗯 - 这是一个判断电话。

用于连接子句的结构通常不为空。数据的东西通常可以为空。

当您有一列包含选择列表之一或 null(未做出选择)时,通常最好为“未做出选择”而不是可为空的列设置特定值。这些类型的列经常参与连接。

于 2009-03-22T02:32:07.163 回答