在使用数据库时,是否有时候使用一对一关系是有意义的?

184

我最近在思考规范化问题,但我发现我无法想象出数据库中应该存在1:1关系的情况。

  • 姓名:社会安全号码(SSN)? 我会将它们放在同一个表中。
  • 人员ID:地址ID? 同样,放在同一个表中。

我可以举出无数个1:多或多:多(带有适当的中间表)的例子,但从未碰到过1:1的情况。

我是否漏掉了一些明显的东西?


当数据库像这样分离时,将其拆分成多个物理设备会更容易。 - Pacerier
还有一个后续问题,如果有意义的话,如何做到呢?可以参考这里和这里。我的问题是如何选择哪个表具有外键约束?我猜这取决于你要解决什么用例... - Nate Anderson
26个回答

183

一个1:1的关系通常表示您已将更大的实体进行了某种分割。通常情况下,这是由于在物理模式中的性能原因,但如果同时期望有大量数据是“未知”的情况下,逻辑方面也可能发生这种情况(在这种情况下,您具有1:0或1:1,但不再支持更多)。

作为逻辑分区的示例:您拥有有关员工的数据,但是如果且仅当他们选择获得健康保险时,需要收集更大的数据集。我会将与健康保险相关的人口统计数据保存在另一个表中,以便更容易进行安全分区,并避免在与保险无关的查询中传输该数据。

物理分区的一个例子是相同的数据托管在多个服务器上。我可能会将有关健康保险人口统计数据保存在另一个州(例如HR办公室所在地),主数据库只能通过链接服务器连接到它...避免将敏感数据复制到其他位置,但使其可用于(在此假设罕见的)需要它的查询。

每当您需要一致的子集的查询更大的实体时,物理分区可以非常有用。


45
一个完美的例子可能是一个包含文件的表格。出于显而易见的原因,您可能希望有一个仅包含文件元数据(文件名、MIME类型等)的表格,以及另一个与之一一映射的表格,其中包含实际的 blob 数据。这将在某些情况下减少查询/排序文件时的开销。 - Kevin Peno
2
是的。这取决于数据库(现代设计只需使用正确的类型将blob存储在文件系统中),即使有这样的支持,人们也必须小心排除列(在SQL中,显式列列表很正常,但某些ORM想要拖动整个记录)。诀窍是了解您的使用模式:如果大多数时间实际数据被忽略,我会使用1:1 blob表。如果大多数访问都是下载数据,则应使用本机存储机制。 - Godeke
@Ochado,虽然我同意你有许多自然发生的(非物理)原因来使用1:1关系,但是“如果你没有历史信息”的限制使得那些情况下我可能不会使用1:1关系来强制执行。 - Godeke
1
@Ochado,我认为IS-A关系是一个很好的例子。我尽量避免在我的表中强制使用面向对象的概念(而是使用ORM作为数据库),但我曾经看到过一些诱人的情况。然而,在大规模系统中,这种系统的性能会受到影响,因此这些实验都失败了。不过,这可能是一个非与性能相关的最佳例子。 - Godeke
实际上,即使存储了历史数据,IS-A和匹配预订情况的关系也很可能保持1:1。 - Tripartio
是的!在考虑1:1关系时很容易忘记1:0的重要作用。1:0-1:1关系与1:1关系非常不同。 - Chuck Le Butt

132

一个原因是数据库效率。1:1关系允许你拆分在行/表锁期间会受到影响的字段。如果表A有大量更新,而表B有大量读取(或来自另一个应用程序的大量更新),那么表A的锁定不会影响表B的操作。

其他人提出了一个很好的观点。根据应用程序等使用系统的方式,安全性也可以是一个很好的理由。我倾向于采取不同的方法,但这是一种限制对某些数据访问的简单方法。在紧急情况下,仅需拒绝对某个表的访问即可。

这是我关于此问题的博客文章。


52

稀疏性。数据关系可能是技术上的1:1,但不必为每一行存在相应的行。因此,如果你有2000万行数据,并且有一些值仅出现在其中的0.5%中,则将这些列推送到可以稀疏填充的表中,可以节省大量空间。


10
但并不是每一行都必须存在相应的行,那就不是1:1了。你说的是1:0,1。 - Mark Brady
1
是的。不知道原帖作者是否有区分。 - chaos
2
我会认为他们这样做了,因为1:0,1有许多用途,包括你的用途,但1:1的用途要少得多。而且他们正在努力寻找用途,所以我认为OP是在区分它们。 - Mark Brady
14
我的工作假设与他相反,因为他列举了1:1、1:many和many:many,但没有提到1:0和1。 - chaos

30
大多数排名靠前的回答都提供了关于1:1关系的有用数据库调整和优化理由,但我想聚焦于"在现实中"自然发生的1:1关系示例。
请注意,大多数这些示例的数据库实现具有一个重要特征:不保留有关1:1关系的历史信息。也就是说,这些关系在任何给定时间点上都是1:1的。如果数据库设计人员想记录随时间变化的关系参与者的更改,则关系变为1:M或M:M;它们失去了1:1的本质。在这样理解的前提下,以下是一些示例:
- "是A"或超类型/子类型或继承/分类关系:此类别表示一个实体是另一个实体的特定类型。例如,可能会有一个员工实体,其属性适用于所有员工,然后有不同的实体来指示特定类型的员工,并具有该员工类型的唯一属性,例如医生、会计师、飞行员等。这种设计避免了多个null,因为许多员工不具有特定子类型的专门属性。此类别中的其他示例可能包括Product作为超类型,并将ManufacturingProduct和MaintenanceSupply作为子类型;Animal作为超类型,并将Dog和Cat作为子类型等等。请注意,每当您尝试将面向对象的继承层次结构映射到关系数据库(例如在面向对象-关系模型中)时,这就是表示此类场景的关系类型。
- "老板"关系,例如经理、主席、总统等,在这种组织单位中只能有一个老板,一个人只能是一个组织单位的老板。如果满足这些规则,则具有1:1关系,例如一个部门的经理,一家公司的CEO等。"老板"关系不仅适用于人员。如果只有一个商店作为公司总部,或者只有一个城市是国家首都,这种关系也会发生。
  • 一些稀缺资源的分配,例如一名员工一次只能被分配一辆公司车(例如每个卡车司机一辆卡车,每个出租车司机一辆出租车等)。最近我的同事给我举了这个例子。

  • 婚姻关系(至少在禁止多夫多妻的法律管辖区):一个人同时只能与另一个人结婚。我从一本教科书中得到这个例子,它将其作为公司记录员工婚姻关系时一对一单一关系的例子。

  • 匹配预订:当唯一的预订被分成两个实体后进行实现。例如,汽车租赁系统可能会将预订记录在一个实体中,然后在另一个实体中记录实际租赁情况。虽然这种情况也可以设计为一个实体,但是将实体分开可能是有意义的,因为并非所有预订都会被实现,也不是所有租赁都需要预订,而且这两种情况都非常普遍。

  • 我再次重申,如果没有记录历史信息,大多数这些关系都是一对一的关系。因此,如果员工在组织中改变角色,或经理负责不同的部门,或者员工重新分配车辆,或者有人丧偶再婚,那么关系的参与者就会发生变化。如果数据库不存储任何有关这些一对一关系的先前历史记录,则它们仍然是合法的一对一关系。但是,如果数据库记录历史信息(例如为每个关系添加开始和结束日期),则它们基本上都会变成M:M关系。

    历史注释有两个明显的例外:首先,某些关系很少更改,因此通常不会存储历史信息。例如,大多数 IS-A 关系(例如产品类型)是不可变的;也就是说,它们永远不会改变。因此,历史记录点无关紧要;这些将始终实现为自然的1:1关系。其次,预订-租赁关系分别存储日期,因为预订和租赁是独立的事件,每个事件都有自己的日期。由于实体具有自己的日期,而不是1:1关系本身具有开始日期,因此即使存储历史信息,这些关系也将保持为1:1关系。


    2
    我非常喜欢你坚持原始问题精神的做法,即关于何时这种关系是正确的,而不是因为计算机的物理特性等世俗原因而变得有用。 - user1852503

    23
    您的问题可以有几种解释方式,这取决于您表述问题的方式。回答也显示了这一点。
    现实世界中数据项之间肯定存在1:1关系。毫无疑问。“是一个”的关系通常是一对一的。汽车是一种交通工具。一辆汽车是一辆车。一个交通工具可能是一辆汽车。有些交通工具是卡车,在这种情况下,一个交通工具不是一辆汽车。一些答案涉及到了这种解释。
    但是我认为你真正想问的是... 当存在1:1关系时,是否应该分开表格?换句话说,您是否应该有两个包含完全相同键的表格?在实践中,我们大多数人只分析主键,而不是其他候选键,但这个问题稍微有些不同。
    1NF、2NF和3NF的规范化规则从不需要将表拆分为两个具有相同主键的表格。我还没有确定将架构放入BCNF、4NF或5NF是否会导致具有相同主键的两个表。直观来看,我猜答案是否定的。
    有一种称为6NF的规范化级别。6NF的规范化规则绝对可以导致具有相同主键的两个表格。 6NF优于5NF的优点在于可以完全避免NULLS。这对一些数据库设计师很重要,但并非所有人都如此。我从未费心将架构放入6NF。
    在6NF中,缺失的数据可以通过省略行来表示,而不是在某些列中使用NULL的行。
    除了规范化之外,还有其他拆分表格的原因。有时,拆分表格会导致更好的性能。对于一些数据库引擎,您可以通过将表格进行分区而获得相同的性能优势,而无需实际拆分它。这样做的好处是保持逻辑设计易于理解,同时为数据库引擎提供加速工具。

    20
    我主要使用它们有几个原因。一个是数据更改速率的显著差异。我的一些表可能会有审计跟踪,我会跟踪记录的以前版本,如果我只想跟踪5列的以前版本而不是10列,那么将这5列分割到具有审计跟踪机制的单独表中更加高效。此外,我可能有一些记录(例如用于会计应用程序),它们只能编写,您不能更改美元金额或其所属的帐户,如果出现错误,则需要创建相应的记录以撤销不正确的记录,然后创建更正条目。我对表格施加了约束,以强制执行它们不能被更新或删除的事实,但是我可能有一些对象属性是可变的,这些对象属性保存在另一个没有修改限制的表中。我再次这样做的另一个时候是在医疗记录应用程序中。与访问相关的数据一旦签名就无法更改,而其他与访问相关的数据则可以在签名后更改。在这种情况下,我会拆分数据并在锁定表上放置触发器,在签名后拒绝对锁定表的更新,但允许对医生未签名的数据进行更新。
    另一个用户在评论中提到1:1不规范化,但在某些情况下,尤其是子类型方面,我不同意这一点。假设我有一个员工表,主键是他们的社会安全号码(这只是一个例子,让我们将是否为好主键的辩论留给另一个帖子)。员工可以是不同类型的,例如临时或永久性,并且如果它们是永久性的,则必须填写更多字段,例如办公电话号码,仅当类型='Permanent'时才应该不为空。在第三正则形式数据库中,列应仅取决于关键字,即员工,但实际上取决于员工和类型,因此1:1关系在这种情况下是完全正常且理想的。它还防止了过度稀疏的表格,如果我有10个通常填充的列,但还有20个仅适用于某些类型的额外列。

    1
    @ShaneD +1,因为有实际的例子。我也喜欢“只读”的区别。 - Michael Riley - AKA Gunny

    14

    我能想到的最普遍的情况是当你有二进制大对象(BLOB)时。假设您需要在数据库中存储大型图像(通常不是最佳存储方式,但有时限制会使它更加方便)。您通常会希望将blob放在单独的表中以提高非blob数据的查找效率。


    真的吗?通常这不是最好的方式吗?最大的在线音乐供应商将它的MP3存储在一个数据库中,啊哈。 - Mark Brady
    1
    @Mark,有一些关于最佳存储图像的方式的问题,无论是在数据库中还是在外部,共识似乎是文件系统更快。我想象如果这确实是真的,那么对于MP3也应该是如此。 - James McMahon
    1
    通常您会希望在单独的表中处理BLOB。如果BLOB超过特定DB特定行长度,则通常不会将其存储为内联。如果BLOB不是内联,则通常将其存储为指向它们在DB页面上物理位置的“指针”。 - blispr
    1
    存储大数据(文件)在数据库中是否有意义是有争议的,一些人支持,一些人反对,但对于1:1的示例可以给予肯定。 - Kevin Peno
    这实际上应该是一个我想看到的独立问题,它需要聪明人对不同硬盘/操作系统/关系型数据库进行时间/性能测量的输入。对于这个问题应该有一个明确的答案,如果正确地测量了的话,就不应该是有争议的。难道没有人已经做过吗? - Stefan

    9

    从纯科学的角度来看,它们是无用的。

    在实际的数据库中,有时将很少使用的字段保存在单独的表中是有用的:可以加快仅使用该字段的查询速度;避免锁定等问题。


    3
    @Mark Brady:不,它没有,因为存在Na2SO4和KCl。在制作宇宙数据库时,您应该创建t_atom(id INT,protons INT,neutrons INT),t_molecule(id INT,atom_id INT)并进行连接。这是一对多的关系。 - Quassnoi
    2
    再想一想,INT 可能不足以满足我们的需求,因为宇宙中有10^78个原子。即使将两个 GUID 组合在一起,也只能容纳1/10的原子数。我们需要一个RAW(40)主键——以防我们的数据库增长过快。你知道的,黑暗物质之类的东西。 - Quassnoi
    1
    哦,所以只有关系理论是纯科学。没想到化学除非我们为它建立一个数据库,否则它不是纯科学。 - Mark Brady
    1
    @Mark Brady:你能不能直接说出你不同意的地方呢?我真的不明白你的讽刺意味 :) - Quassnoi
    @Mark Brady:实际上,NaCl更好的描述方式是由交替排列的Na和Cl原子构成的立方晶体。 NaCl公式是一个方便的缩写,在化学方程式中起作用,但它并不是真正的物质。我认为没有人实际上看到过明显的NaCl分子。 - Adriano Varoli Piazza
    显示剩余6条评论

    9

    有时候,为了限制对字段的访问,不使用视图而是将受限字段保留在单独的表中,并且只允许特定的用户访问这些表。


    8
    我可以想象出一些情况,你在OO模型中使用继承,并且需要将继承树持久化到数据库中。
    例如,你有一个类Bird和Fish,它们都继承自Animal。在你的数据库中,你可以有一个'Animal'表,其中包含Animal类的公共字段,Animal表与Bird表具有一对一的关系,同时也与Fish表具有一对一的关系。
    在这种情况下,你不必拥有一个Animal表,其中包含很多可空列来保存Bird和Fish属性,在记录代表鸟时,所有包含Fish数据的列都设置为NULL。
    相反,你在Birds表中有一条记录,该记录与Animal表中的记录具有一对一的关系。

    这个答案与另一个问题有关:即1到0-1关系的问题。 - Hibou57

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接