过度使用数据库中的可空列是“代码异味”吗?
Posted
技术标签:
【中文标题】过度使用数据库中的可空列是“代码异味”吗?【英文标题】:Is an overuse of nullable columns in a database a "code smell"? 【发布时间】:2009-06-23 20:19:14 【问题描述】:我刚刚进入一个项目,它有一个相当大的数据库后端。我已经开始深入研究这个数据库,发现 95% 的字段都可以为空。
这是数据库世界的正常做法吗?我只是一个低级程序员,而不是 DBA,但我认为您希望将可空字段保持在最低限度,仅在有意义的地方。
如果大多数列都可以为空,这是否是“代码异味”?
【问题讨论】:
如果 DB 架构仍在发展并且正在添加新列,则在开始时将新列设置为空可能会更容易,如果添加了很多列,这可能意味着很多空到数据库中的表。这似乎是我会看到它们出现的地方。 如果不能依赖默认值,将新列引入表中作为 NOT NULL 也是 stupidly-hard task。 【参考方案1】:根据我的经验,默认值通常是例外,而 NULL 是常态。
确实,空值很烦人。
它也非常有用,因为 null 是“NO VALUE”的最佳指标。具体的默认值非常具有误导性,您可能会丢失信息或在未来引入混乱。
【讨论】:
OP 没有说他们是否使用 mysql。 MySQL 手册说:“如果可能,将列声明为 NOT NULL。通过更好地使用索引并消除测试每个值是否为 NULL 的开销,它使 SQL 操作更快。您还可以节省一些存储空间,每列一位。 ...”dev.mysql.com/doc/refman/5.5/en/data-size.html【参考方案2】:任何开发过数据输入应用程序的人都知道,某些字段在输入时未知是多么常见——即使对于业务关键的列,以解决@Chris McCall 的回答。
然而,“代码异味”只是表明某些东西可能被草率地编码了。您使用气味来识别需要更多调查的事情,而不一定是必须改变的事情。
所以,是的,如果您如此一致地看到可为空的列,那么您的怀疑是正确的。它可能表明有人懒惰,或者害怕明确声明NOT NULL
列。您可以证明自己的分析是合理的。
【讨论】:
【参考方案3】:我属于 Extreme NO 阵营:我一直避免使用 NULL。抛开关于它们实际含义的基本考虑(因为与不同的人交谈,你会得到不同的答案,例如“无价值”、“未知价值”、“缺失”、“我的姜猫叫 Null”),最糟糕的问题NULL 的原因是它们经常以神秘的方式破坏您的查询。
我已经记不清我必须调试某人的查询的次数(好吧,可能是 9 次)并将问题追溯到针对 NULL 的连接。如果您的代码需要 ISNULL 来修复连接,那么您很可能也失去了索引的适用性和性能。
如果您确实必须存储“缺失/未知/null/cat”值(这是我希望避免的),最好明确说明。
那些擅长 NULL 的人可能不同意。使用 NULL 往往会将 SQL 人群分散到中间。
根据我的经验,大量使用 NULL 与数据库滥用正相关,但我不会将其作为自然法则刻在石碑上。我的经验就是我的经验。
编辑:额外的想法。像我这样反对零的种族主义者可能比支持零的人更兴奋于规范化。我认为***的规范化者不会对他们的表上可能带有 NULL 的参差不齐的边缘感到太高兴。大量的空值可能表明数据库开发人员没有进行大量的规范化。因此,与其 NULL 暗示代码是“坏的”,不如说它可能暗示开发人员对规范化的哲学立场。也许这正在达到。只是一个想法。
【讨论】:
整数值零,使用不当会导致除零错误,您怎么看?这是否意味着我们应该禁止使用零? 另外,您的 cat 示例是虚假的。字符串“Null”与 SQL NULL 不同。但这确实让我想知道如果那只猫失踪了,你将如何制作一张海报寻找那只猫。 ;-) 我的意思是,零和 NULL 都值得一些特殊处理。 ANSI SQL 对 NULL 的标准语义很清楚(尽管 Oracle 的 VARCHAR2 行为)。任何不能区分 NULL 和 'Null' 的人都不会在课堂上专心! 我明白你的意思,但我认为 NULL 与零非常相似,因为在编写某些类型的表达式时必须检查是否存在 NULL。我确实理解 NULL 的语义,但我坚持认为,正确使用它是语言中有用且有效的部分。仅仅因为一些开发人员不了解它的工作原理并不意味着我们应该制定禁止使用它的全面规则。 我完全同意。正如我所指出的——那些熟练使用 NULL 的人可能不同意我的立场。撇开关于关系基础的争论不谈,我将我的立场总结为:NULL 在操作中是如此特殊,以至于它比将它们从解决方案中规范化更有可能造成伤害。在十多年的 SQL 工作中,我遇到了许多使用 NULL 的人,但很少有人意识到它的微妙之处 [也许这是我所在领域的症状 - 但这是另一个话题]。比尔,我算你在意识到 =)【参考方案4】:不知道我是否认为这总是一件坏事,但如果添加列是因为单个记录(或可能有几个)需要具有值而大多数不需要,那么它表示一个非常平坦的表结构体。如果您看到诸如“addr1”、“addr2”、“addr3”之类的列名,那就太难看了!
我敢打赌,您拥有的大部分列都可以删除并在其他表中表示。您可以通过外键关系找到“非空”的。这将增加您将要执行的连接,但执行“where not col1 is null”可能更有效。
【讨论】:
除了名为 addr1、addr2、addr3 的列之外,您将如何存储地址的各个行? (或者您指的是 3 个单独的完整地址?)地址是空值的标准示例案例之一。有些地址有 2 行,有些有 6 行。 我通过 AddrX 将 Addr1 解释为 mailingAddress、physicalAddress、workAddress、xmasAddress 等的占位符。否则,它将是 AddrLine1、AddrLine2。 是的,也许地址是一个不好的例子——可能应该使用电话号码。通常出现的(在错误的模式中)是“homeaddr”、“workaddr”、“vacationaddr”、“otheraddr”、“otheraddr2”等,所有这些都是因为一个记录需要“workaddr”,另一个需要“vacationaddr”(没有“ workaddr") 等等。 AddrLine1 和 AddrLine2 没问题。【参考方案5】:我认为应该避免使用可为空的列。只要域的语义可以使用明确指示缺失数据的值,就应该使用它而不是 NULL。
例如,让我们想象一个包含Comment
字段的表。大多数开发人员会在此处放置 NULL 以指示该列中没有数据。 (并且,希望有一个不允许零长度字符串的检查约束,以便我们有一个众所周知的“值”来指示缺少值。)我的方法通常是相反的。 Comment
列是NOT NULL
,零长度字符串表示缺少值。 (我使用检查约束来确保零长度字符串确实是零长度字符串,而不是空格。)
那么,我为什么要这样做?两个原因:
NULL
s 需要 SQL 中的特殊逻辑,而这种技术可以避免这种情况。
许多客户端库都有特殊值来表示NULL
。例如,如果您使用 Microsoft 的 ADO.NET,则常量 DBNull.Value
表示 NULL,您必须对此进行测试。在 NOT NULL
列上使用零长度字符串可以避免这种需要。
尽管如此,在很多情况下NULL
s 是可以的。事实上,我不反对在上述场景中使用它们,尽管这不是我的首选方式。
无论您做什么,都要善待那些将使用您的桌子的人。 保持一致。让他们放心地SELECT
。让我解释一下我的意思。我最近在一个项目中工作,该项目的数据库不是我设计的。几乎每一列都可以为空并且没有约束。关于什么代表价值的缺失没有一致性。它可能是NULL
,一个零长度的字符串,甚至是一堆空格,而且通常是这样。 (我不知道这些价值观是如何到达那里的。)
想象一下,在这种情况下,开发人员必须编写丑陋的代码来查找所有缺少Comment
字段的记录:
SELECT * FROM Foo WHERE LEN(ISNULL(Comment, '')) = 0
令人惊讶的是,尽管可能会影响性能,但仍有开发人员认为这是完全可以接受的,甚至是正常的。更好的是:
SELECT * FROM Foo WHERE Comment IS NULL
或者
SELECT * FROM Foo WHERE Comment = ''
如果你的表设计得当,上面两条 SQL 语句就可以产生高质量的数据。
【讨论】:
我不同意。 NULL 表示未知,与列的数据类型无关。它应该总是用来表示未知,而像空字符串这样的魔法值不应该用来表示未知。 另一方面,如果我们知道用户选择不发表评论,我们为什么要使用意味着“未知”的东西来表示该知识? @john-saunders 这取决于域。注释字段中的零长度字符串可以表示一个定义非常明确、非常知名的值:“无注释”。这与“未知”完全不同。然而,这些都是连科德博士都玩过的语义游戏。后来他想出了几个 NULL 的替代方法来表示未知、丢失等。这里重要的是保持一致。 因为评论的值是未知的。 对于评论示例,空值应表示“我们从未要求用户发表评论”,而“”值应表示“我们向用户提供了一个带有可选评论框的表单,并且他们提交了表格,但没有在里面放任何东西”。在大多数情况下,我可以设想有一个“cmets”列,后一种情况是唯一可能的情况。【参考方案6】:简而言之,我会说是的,这可能是代码异味。
一列是否可以为空是非常重要的,应该仔细确定。应该对每一列的问题进行评估。我不相信NULL
的单一“最佳实践”默认值。对我来说,“最佳实践”是在表的设计和/或重构期间彻底解决可空性问题。
首先,您的主键列都不能为空。然后,对于任何外键,我强烈倾向于NOT NULL
。
我考虑的其他一些事情:
应强烈避免使用NULL
的标准:
money
columns - 真的有可能这个金额是未知的吗?
NULL
最常被证明的标准:
datetime
列 - 没有保留日期,因此 NULL
实际上是您的最佳选择
其他数据类型:
char
/varchar
列 - 用于代码/标识符 - NOT NULL
几乎完全
int
列 - 主要是 NOT NULL
除非您想区分未知响应的“儿童数量”。
【讨论】:
【参考方案7】:不,字段是否可以为空是一个数据概念,不能是代码异味。 NULL 是否对代码来说很烦人与拥有可为空的数据字段的用处无关。
【讨论】:
【参考方案8】:恐怕它们是(非常常见的)气味。查找有关该主题的 C.J. Date 著作。
【讨论】:
真的吗? C. J. Date 认为 NULL 不是关系模型的合法部分,即使它们是,在 SQL 中实现错误。所以他关于这个话题的著作可能被认为是一种极端的观点。 如果不是因为他是对的,他们可能会被认为是一种极端的观点,并且对于他为什么是对的(至少就“[nulls]在 SQL 中实现错误”部分)。像 Optional 或 Maybe (或任何你想叫它的任何东西)这样的类型生成器是有用的,但是当 Missing = Missing 计算为一个名为 Unknown 的神奇第三个布尔值时,它具有各种古怪和不一致的属性,这对每个人来说都是一个问题(包括优化器)。【参考方案9】:作为最佳实践,如果列不应为空,则应将其标记为空。但是,我不相信这样的事情会完全发疯。
【讨论】:
【参考方案10】:我想是的。如果您不需要数据,那么它对您的业务并不重要。如果它对您的业务很重要,则应该是必需的。
【讨论】:
当然,在用户购买任何东西之前需要一个信用卡号(例如),但他们仍然应该被允许保存其他属性,然后再添加信用卡号。如果因为他们没有必填字段而阻止他们输入任何数据,那只会让他们不高兴。 这就是为什么这些东西不属于同一个表,而不是为什么信用卡号在信用卡订单表中应该可以为空,对吧? 这只是一个例子。关键是给定表的某些属性可能对您的业务很重要,但对数据完整性不重要。 我还没有看到一个可行的例子,证明这是真的而不是设计工件。【参考方案11】:这完全取决于项目的范围和要求。我不会单独使用可空字段的数量作为编写或设计不佳的代码的指标。看一下业务领域,如果那里表示有许多在数据库中可以为空的不可为空字段,那么您就有一些问题。
【讨论】:
【参考方案12】:根据我的经验,当 Null 和 Not Null 与必填字段/非必填字段不匹配时,会出现问题。
这些实际上都是可选字段,这是有可能的。如果您在业务层或 UI 层发现这些字段是必需的,那么我认为这意味着数据模型已经偏离业务对象模型,并且是过于保守的数据库更改策略或疏忽的标志。
如果您在数据上运行示例数据生成器,然后尝试根据 SQL 加载有效的数据,您会立即发现规则是否匹配。
【讨论】:
【参考方案13】:这似乎很多,这可能意味着您至少应该调查一下。请注意,如果这是具有大量数据的成熟产品,说服任何人改变结构可能很困难。在设计阶段越早发现此类问题,就越容易修复所有相关代码以适应变化。
他们使用空值是否不好取决于允许空值的列是否看起来应该是相关的表(家庭电话、手机、商务电话等应该在单独的电话表中)或者它们看起来比如可能不适用于所有记录的东西(可能是具有一对一关系的相关表)或在数据输入时可能不知道(可能没问题)。我还会检查它们是否实际上总是有值(如果业务逻辑确实需要该信息,那么您可能可以将其更改为非空值)。如果您有几条为 null 的记录
【讨论】:
【参考方案14】:根据我的经验,像您这样的大型数据库中有很多可以为空的字段是很正常的。考虑到它可能被不同的人编写的许多应用程序使用。使列可以为空很烦人,但这可能是保持应用程序健壮性的最佳方式。
【讨论】:
这是令人沮丧的普遍现象;这不好,通常不会使应用程序健壮。【参考方案15】:将继承(例如 c# 对象)映射到数据库的多种方法之一是为层次结构顶部的类创建一个表,然后为所有其他类添加列。当不同子类的对象存储在数据库中时,这些列必须可以为空。这称为Single-table inheritance mapping(或Map Hierarchy To A Single Table),是一种标准设计模式。
单表继承映射的一个副作用是大多数列都可以为空。
此外,在 Oracle 中,空字符串(长度为 0)被视为 null,因此在某些公司中,即使在 SqlServer 上,所有字符串列也可以为 null。 (仅仅因为第一个客户想要 SqlServer 上的软件并不意味着第二个客户没有不会让 SqlServer 进入那里的网络的 Oracle DBA)
【讨论】:
然而,当它到达大多数列为空的阶段时,我认为是时候考虑映射到多个表了。这样就可以对派生表实施一些约束。 但是更改应用程序使用的 ORM 系统可能是一个很大的风险。最后,数据库是为应用程序服务的,而不是相反。 (毕竟我是 C# 程序员而不是 DBA) 谁说过要改变 ORM 系统?只需更改 ORM 映射到底层数据库的方式即可。此外,这可以允许强制执行额外的约束,从而提高整个系统的质量。 假设5年前选择的ORM系统让你 作为一个非常反对 DBA 的开发人员,我会说对这些表的约束首先是天生不好的。我完全同意 Ian 的观点,数据库旨在为应用程序提供服务,我经常看到数据库指示应用程序。这是错误的。【参考方案16】:抛出相反的意见。数据库中的每个字段都应该可以为空。没有什么比使用数据库更令人沮丧的了,该数据库在每次插入时都会引发关于 required this 或 required that 的异常。什么都不需要。
有一个例外,钥匙。显然所有的主键和外键都应该强制存在。
应用程序的工作应该是验证数据,而数据库则应该简单地存储和检索您提供的内容。让它处理验证逻辑,甚至像 null 或 not null 这样简单,都会使项目的维护变得更加复杂,因为不同的规则分布在所有内容上。
【讨论】:
【参考方案17】:正如其他人所提到的,前端数据输入应该允许省略许多字段。人们如何解释NULL
的三元性质(例如,空与缺失)使这一点变得复杂。
因此,我只回答数据库设计的一个方面:外键。
在一般中,外键不受业务逻辑的任意性影响,因此看到这些允许NULL
的列绝对是代码异味。
例如,如果您有一个[Person]
表,那么在任何情况下您都不会有一个[Person].[FatherID]
值是NULL
故意。
对于大型数据库,尝试将NULL
保存到这样的列可能会在某些时候发生,因为存在不可避免的错误,通过NOT NULL
约束可以更快地发现这些错误。因此,对于版本 1 或表,您永远不应该允许可以为空的列没有理由。
但是在不断发展的代码库中事情变得更加棘手,尤其是那些保持在线并因此需要迁移脚本来升级的代码库。特别是,您稍后可能会发现可以为空的列添加到表中,因为根据您的集成过程,将它们正确添加为不可为空的可能是quite hard。
此外,可视化表设计器(例如在 SQL Server Management Studio 和 Visual Studio 中)默认允许NULL
,因此这可能只是代码审查不足的问题。
我不想为标志(即布尔值)列尝试正确的答案,但我强烈建议考虑如何在不允许 NULL
的情况下实现它们,因为我通常会找到避免可空性的方法在业务逻辑的约束下。
【讨论】:
以上是关于过度使用数据库中的可空列是“代码异味”吗?的主要内容,如果未能解决你的问题,请参考以下文章