为什么单一主键比复合主键更好?

31
为什么要拒绝使用复合键,而是选择所有表都使用一个名为“id”的单一主键?因为通常所有ORM都遵循这个原则。
编辑:
我刚开始学习Ruby on Rails,在Pragmatic的《敏捷开发》书中有一句话:Rails确实不太适合每个表都有数字主键。它对列的名称不太挑剔。
当我学习Doctrine时,也读到了类似的话。
第二次编辑:
请也查看这个链接。我越来越困惑这件事情了: Composite primary keys versus unique object ID field 从上面的链接中得知:
*主键应该是恒定和无意义的;非代理键通常一个或两个要求失败,最终会出现问题。
如果键不是常量,则将来会出现更新问题,这可能会变得相当复杂。 如果键没有意义,则更有可能改变,即不是常量;请参见上文
以一个简单而常见的例子来说:存货项目表。可能会诱人将项目编号(SKU编号、条形码、零件代码或其他)作为主键,但是一年后所有项目号都改变了,你就需要解决整个数据库更新的问题……
编辑:还有一个比哲学更实际的问题。在许多情况下,您会找到特定的行,然后更新它或再次找到它(或两者兼而有之)。对于复合键,需要跟踪更多的数据,并在重新查找或更新(或删除)的WHERE从句中应用更多的约束条件。而且,在此期间,其中一个键段可能已经发生了变化!使用代理键时,始终只需保留一个值(代理ID),并且根据定义,它不会改变,这大大简化了情况。

4
请列出你的参考来源吗?我同时使用组合键和基于序列的ID主键,有时候一个比另一个更为适合。 - FrustratedWithFormsDesigner
你正在使用哪个ORM,它主张始终使用简单键而不是复合键?我从未遇到过任何不与复合键良好配合的ORM。据我所知,没有什么好理由给例如连接表一个自己的代理键。 - Iain Galloway
@FrustratedWithFormsDesigner @Iain Galloway 我在学习Doctrine时读到了这篇文章,现在在学习Ruby on Rails时也遇到了同样的问题。 如果您想知道书中写这行代码的确切页码,请告诉我。我是初学者,所以我只是想了解真正的事情,而不是质疑或辩论为什么要使用这个ID字段。 - Mohit Jain
在这种情况下,我认为在Ruby-on-Rails和Doctrine的上下文中,“主键比复合键更好”这个说法可能是正确的。这可能是由于底层架构(但我不是Rails或Doctrine的专家,因此无法进一步安全地评论;)),但是不应该自动假定该语句适用于其他环境/ORMS。 - FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner 看看编辑部分,检查一下那个链接。 - Mohit Jain
9个回答

40

我认为没有一种笼统的说法,即你应该只使用一个名为id的单一主键。

大多数人使用代理主键作为自动生成的整数主键,因为它将主键与需要更改的情况(例如,如果您将PK设置为用户名,而用户后来更改了其法定名称)隔离开来。 如果您要更新PK和所有FK列以反映新名称,则必须执行此操作。 如果您使用代理主键,则只需在一个位置更新用户的名称(因为表使用整数而不是名称连接)。

主键的大小很重要,因为PK会复制到您在表上构建的每个索引中。 如果PK很大(如字符串),则索引中每页的键都较少,并且索引将占用更多缓存内存来存储它。 整数很小。

具有自动递增整数主键很容易成为聚集索引,因为行按此顺序存储,而不需要返回并推出行以插入新行,您始终向表的结尾添加新行。


顺序 GUID 也非常有效,特别是当您需要将来自多个数据库的数据集合并到单个表中时。 - Juliet
@Juliet,但它们很大并浪费索引空间。如果可能的话,最好将自动递增的int ID与另一个列组合起来,该列标识数据库/公司/等等(如1或两个字节的int),因为它可以使用比GUID少得多的空间,但仍然在所有数据库中是唯一的。 - KM.
4
除了特殊情况,我不建议使用GUID。 - James Westgate
我刚开始学习Ruby on Rails,在《敏捷开发》这本书中有一句话:--- Rails真的不太适合除非每个表都有一个数字主键。它对列名不那么挑剔。 当我学习Doctrine时,也读到了类似的话。 - Mohit Jain

18

使用复合键遇到的唯一真正限制是在使用带有子查询的IN表达式时。这是个问题,因为在IN表达式中的子查询必须返回单列(至少在T-SQL中是如此)。

SELECT
    emp.Name,
    emp.UserDomain,
    emp.UserID
FROM
    employee emp
WHERE
    ???? IN (SELECT e.UserDomain, e.UserID FROM ... /* some complex 
                                                       non-correlated subquery 
                                                       or CTE */
            )

当然,总是有方法可以解决问题的,但有时候这可能会很烦人。

这并不是在合适的情况下避免使用复合键的理由。


10

你可以同时使用两种方式。在某些情况下,当建立实体之间的关联时,你可以使用实体键作为组合键。

通常情况下,我会为实体使用生成的 ID,并为关系使用组合键。


1
这是我使用的相同方法,而且效果非常好。 - Johnie Karr

8

基本上是关于保持联接(JOIN)简单的问题 - 哪一个更容易理解:

SELECT
   p.ID, p.Name, p.City,
   c.ID, c.Country, c.ISOCode
FROM
   dbo.Parent p
INNER JOIN
   dbo.Child c on c.ParentID = p.ID

或者

SELECT
   p.ID, p.Name, p.City,
   c.ID, c.Country, c.ISOCode
FROM
   dbo.Parent p
INNER JOIN
   dbo.Child c ON c.ParentName = p.Name
     AND c.ParentCity = p.City
     AND c.ParentCountry = p.Country

如果您有复合主键,那么从子表连接到您的表时,必须“拖着”所有这些列,并且所有这些列也将存在于子表中,JOIN语句会变得非常混乱。最好为JOIN使用单个(即使是替代)键!


你会建议在代表多对多关系的连接表中使用代理键(而不是组合键)作为主键吗?如果是,为什么? - Iain Galloway
1
@Iain Galloway:不一定-对于多对多关系的连接表,使用复合主键可能是你受益的特殊情况之一。但即使在这种情况下增加一个辅助主键也没有什么坏处(除了额外的列)-这将使您更轻松地管理(例如删除)该连接表中的条目。 - marc_s
如果您的连接表变得不仅仅是一个连接,而具有自己的属性和特性,则拥有单个代理主键可能会有所帮助。特别是如果您在联接表上挂载其他表格...如果您为PK保留了组合密钥,则所有这些OK列都必须复制到这些其他表格中。使用单个PK,子表仅向连接表的单个PK列添加单个FK列。随着时间的推移,单个PK列可以更好地支持架构更改和DB设计增长。 - ryancdotnet

5
我曾经参与开发一个应用程序,其中使用了11列作为主键。每次我想保证只更新一行时,都需要一遍又一遍地重新输入列表,这非常麻烦,也容易引发bug。而且,MS-Access无法处理超过10列的PK等问题。
大型复合键是设计上的不良信号,说明表格包含异构实体,或者设计师不确定每个实体的唯一性。比如假设头发颜色、眼睛颜色和体重足以唯一标识员工,这并不是一个好的键,因为你需要越来越多的列来使其有效工作,最终会包括一些易变的字段,如体重,或对于某些人来说是头发颜色或缺失。

4
尽管我同意其他回答者给出的大多数理由,但我更喜欢单列整数键的主要原因是它使编写用户界面变得更加容易。
如果您使用某种列表控件来表示数据(列表、列表视图、组合框等),则可以通过与项一起存储的单个整数值将每个条目唯一地关联到其数据库表示形式。大多数预先编写的组件已经允许您附加一个整数到每个项目中,对于那些不允许的组件,扩展组件来实现这一点非常容易。
如果您正在服务器应用程序和网页之间传递数据,则将单个标识值存储在表示数据的小部件的id属性中比组成和解析多值id要容易得多。

1
  1. 对于ORM来说,一个单一的标识列,如table_id这样的一致命名比复合键更容易。但是每个好的ORM都支持复合键。

  2. 数据库可以轻松地“自动递增”一个简单的主键,但对于复合键则不行。

  3. 一个简单的主键在查询中也更容易使用。当你需要连接时,你只需要使用两个关系中的一个列。

这并不是说简单的主键比复合主键更好。


0
在面向对象编程中,对象具有独特的身份,而与其内容无关。关系型数据库中的行(元组)仅通过其内容进行标识。因此,在真正进行ORM(对象关系映射)时,即将对象从面向对象编程语言映射到关系型数据库时,必须提供额外的ID,以区别于程序中对象拥有的字段和/或属性 - 除非其中一个或多个已知以唯一方式标识对象。

你的表格肯定需要一个主键,但这个主键不一定只能是单列。复合主键非常常见,尤其是在连接表上。 - Iain Galloway
请再次阅读。我没有说在关系型数据库模式中需要主键(其实不需要),也没有说在设计关系型数据库模式时不会涉及到组合键(确实会)。我的意思是,如果你的关系型数据库模型是从OO类模型盲目生成的,那么所有的表都需要有代理ID作为它们的主键,这些ID与对象类的任何属性都没有对应关系。交叉表将不会生成,除非你以特殊的方式处理集合值属性。 - reinierpost
你有没有一种特殊的方法来表示m:m关系而不使用连接表? - Iain Galloway
当然,我在某个地方想念你。你说我对集合的处理方式“特别”,但我不明白你的替代方案是什么。如果要处理像我建议的“集合值属性”这样的东西,你会如何处理,以避免需要联接表? - Iain Galloway
如果你不特别对待它们,而是以通用的方式进行翻译,那么你最终不会得到连接表。通用翻译将把每个类都翻译成一个带有ID列和每个字段的一列的表,因此确切的列取决于集合的内部实现,例如链表将具有当前和下一个列,数组索引和值。ID用作属性的值。 - reinierpost
显示剩余3条评论

0

你的问题与代理键(或人工键)与自然键的选择密切相关。我认为并不是复合键使用较少,而是自然键(无论是复合还是简单)比人工键更不受青睐。

传统的关系型数据库理论主要处理“自然”键(从业务领域的角度具有意义的键),在这种情况下,复合键经常被发现...自然地。

但是在后来的几年中,数据库设计更倾向于(虽然不是完全)采用“人工”(代理)键模式,通常是一个没有业务含义的顺序号,仅用于唯一标识表中的记录(以及上层对象)。


1
当使用代理键时,复合键仍然可以“自然”地找到。代理键与自然键是完全不同的讨论! - Iain Galloway
阅读问题:“为什么要拒绝复合键,转而使用所有表都使用一个名为id的单个主键。” “名为id的单个主键”恰好是代理键。 - leonbloy
1
我仔细阅读了问题。他根本没有提到自然键。关于自然键与代理键以及简单键与复合键的争论是完全不同的。在没有自然键的情况下,经常出现复合键。例如,请参见http://megocode3.wordpress.com/2008/01/04/understanding-a-sql-junction-table/,其中有一个相关的例子。你能想到一个给那个连接表一个代理键的好理由吗? - Iain Galloway
首先,“代理键 vs 自然键”的“辩论”对于这个问题确实是相关的(请看已接受的答案),并且对于提问者来说是必读的。其次,像你示例中的联合表肯定有一个“自然”的(在两个意义上都是)主键,它根本不需要代理键;然而,事实是现在许多人都有“通用代理键设计”的(好或坏的)习惯,为所有表使用一个“id”代理键(如问题所述);因此,即使在你的示例中,“自然”键也不会被用作主键 - 只是一个唯一限制。 - leonbloy
当然,我已经看到了一种趋势,即在所有表格(包括连接表)中使用自动编号PK。 OP也看到了这一点,并问“为什么”。 - Iain Galloway

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