【问题标题】:Should I use composite primary keys or not?我应该使用复合主键吗?
【发布时间】:2009-06-08 07:42:22
【问题描述】:

在 Java 的 JPA(通过 EmbeddedId 或 IdClass 注释)中似乎只有对复合数据库键的第二类支持。当我阅读复合键时,无论使用何种语言,人们都会觉得它们是坏东西。但我不明白为什么。这些天仍然可以使用复合键吗?如果没有,为什么不呢?

我找到了一个同意我的人: http://weblogs.sqlteam.com/jeffs/archive/2007/08/23/composite_primary_keys.aspx

但另一个不这样做的人: http://weblogs.java.net/blog/bleonard/archive/2006/11/using_composite.html

只有我一个人,还是人们无法区分复合键在哪里合适?我看到复合主键在表不代表实体时很有用 - 即当它代表连接表时。

一个简单的例子:

Actor { Id, Name, Email } Movie { Id, Name, Year } Character { Id, Name } Role { Actor, Movie, Character }

在这里,Actor、Movie 和 Character 显然受益于将 Id 列作为主键。

但是 Role 是一个多对多连接表。我认为创建一个 id 只是为了识别数据库中的一行是没有意义的。对我来说,主键是{ Actor, Movie, Character } 似乎很明显。这似乎也是一个相当有限的功能,特别是如果连接表中的数据一直在变化,一旦主键序列回绕到 0,您可能会发现主键冲突。

那么,回到最初的问题,使用复合主键仍然可以接受吗?如果没有,为什么不呢?

【问题讨论】:

  • Character { Id, Name }——这肯定是支持复合键的论据吗?我的意思是,我看不出将角色与它出现的电影分开建模有什么价值。是否所有名为“罗宾汉”的角色都使用相同的 ID?如果一部电影中的“罗宾汉”不是人们所期待的林肯绿紧身衣人物呢?

标签: java sql language-agnostic database-design


【解决方案1】:

在我个人看来,由于以下几个原因,您应该避免使用复合主键:

  1. 未来的变化:当您设计数据库时,您有时会错过将来会变得重要的东西。一个重要的例子是认为两个或多个字段的组合是唯一的(因此可以成为主键),而将来您希望在其中允许 NULL 或其他非唯一值。拥有单个主键是针对此类更改的一个很好的可靠解决方案。

  2. 统一性:如果每个表都有唯一的数字 ID,并且您还对其名称保持一些标准(例如“ID”或“tablename_id”),则引用它的代码和 SQL 会更清晰(在我的意见)。

还有其他原因,但这些只是其中的一小部分。

我要问的主要问题是,如果您有一组唯一的字段,为什么不使用单独的主键?费用是多少?一个额外的整数索引?这还不算太糟糕。

希望对您有所帮助。

【讨论】:

  • 我发现围绕未知的“未来范围”进行设计是不明智的。以我的经验,它弊大于利——倾向于过度复杂化。
【解决方案2】:

我认为使用复合键没有问题。

对我来说,数据库本身就是一个组件,应该像对待代码一样对待它:例如,我们想要干净的代码,清楚地传达它的意图,做一件事并且做得很好,那不t 增加任何不必要的复杂程度等。

db也是一样,如果PK是复合的,这就是现实,所以模型要保持干净清晰。复合 PK 比混合自增 + 约束更清晰。当你看到一个 ID 列什么都不做时,你需要问一下真正的 PK 是什么,是否还有其他需要注意的隐藏事项等。清晰的 PK 不会留下任何疑问。

数据库是您的应用程序的基础,对我来说,我们需要我们可以拥有的最坚实的基础。在此基础上,我们将构建应用程序(网络或非网络)。所以我不明白为什么我们应该弯曲数据库模型以符合一种开发工具/框架/语言中的某些特定内容。数据正在指导应用程序,而不是相反。如果 ORM 在未来发生变化并变得过时并且出现了一个更好的解决方案来强加另一种模型怎么办?我们不能使用 db 模型来适应这个或那个框架,模型应该保持不变,它不应该依赖于我们使用什么工具来访问数据......

如果 db 模型在未来发生变化,它应该会发生变化,因为功能发生了变化。如果我们今天知道此功能将如何变化,我们将对此进行建模。并且任何未来的变化都会在时机成熟时得到处理,例如我们无法预测对现有数据的影响,因此额外的一列并不能保证它会保留任何未来的变化......

我们应该针对今天的功能进行设计,并尽可能保持 db 模型最简单,这样将来它很容易改变/发展。

【讨论】:

    【解决方案3】:

    宗教战争已经并且仍然在这个主题上进行。

    OO 人对“身份”有这种热情,他们会告诉你,唯一重要的是你能够在程序中“识别”“现实生活中的对象”,以及复合的“真实-生活”的钥匙只会在你试图实现这个目标时给你带来麻烦。

    数据人员对 OO 方面认为是“热心”的“独特性”有这种看法,并且会告诉您,唯一重要的是,如果业务告诉您属性的组合(值) X 和属性 Y 必须是唯一的,那么您的工作就是确保数据库强制执行组合 X+Y 的唯一性业务规则。

    您希望如何回答您的问题只是您喜欢哪种宗教的问题。我个人的宗教信仰是数据一。自 1969 年以来,该宗教已被证明能够经受住任何炒作和趋势。

    【讨论】:

      【解决方案4】:

      Similar questions 已被问到 SO,但没有达成共识;)

      如果您开发 Web 应用程序,您会喜欢单列 pk,因为它们使您的 URL 更简单。

      要包装一个序列,您需要在一个表(32 位)中包含 20 亿条记录,或者 10^18 条记录和 64 位 pk。

      顺便说一句,您的数据模型不允许包含未知演员的电影角色。

      【讨论】:

        【解决方案5】:

        我的一般意见是……不。不要使用复合主键。

        如果您使用它们,它们通常会使 ORM 复杂化(ORM 有时甚至将复合主键称为“遗留行为”),而且通常如果您使用多个键,它们中的一个或多个往往是自然的,而不是比技术键,这对我来说是更大的问题:恕我直言,您当然应该偏爱技术主键。

        Database Development Mistakes Made by AppDevelopers 了解更多信息。

        【讨论】:

          【解决方案6】:

          这是一个宗教的事情。我使用自然键并避免代理。无论是在理论上还是在实践中,我对复合键都没有问题。

          只有最简单的逻辑模型才不会涉及复合键。叫我懒惰,但我认为没有必要通过在实现时将代理项引入物理模型来使数据模型复杂化。当然,如果发现性能问题,我会考虑在桌子上放一个,但我采用与非规范化相同的方法,即作为最后的手段。习惯性地使用代理相当于过早优化,IMO。

          【讨论】:

            【解决方案7】:

            在 Ruby for Rails 中,如果没有明确指定,您的 Role 表将类似于您所描述的(如果这些列实际上是其他表中的 ID)。尽管如此,在数据库中,您可能希望通过在这三列上定义唯一索引来确保唯一组合,即使只是为了帮助数据库优化您的查询。有了这个唯一索引并且框架不使用任何其他主键,您的Role 表中就不需要额外的数字主键。话虽如此,唯一索引完全可以定义为复合主键。

            至于未来的变化:为您的第一次迭代定义一个严格的数据库将防止意外数据被持久化,这将使迁移更加容易。

            所以:我会使用复合主键。

            【讨论】:

              【解决方案8】:

              我只会在连接表中使用它们。绝对确保每个记录标识符随着时间的推移都是唯一且一致的唯一方法是使用合成密钥。

              复合键在理论上看起来不错,这就是为什么它们很想使用,但实践表明它们通常表明您的数据模型存在缺陷。更糟糕的是,在许多情况下,如果数据集足够大,它们将无法保证唯一性。并且数据集总是随着时间的推移而增长,因此使用它们可能意味着您在应用程序中埋下了炸弹,只有在应用程序投入生产使用一段时间后才会爆炸。

              我认为人们低估了 ORM。每种主流编程语言都有一个事实上的 ORM,并且已经存在多年,因为它们解决了 OO 和关系结构之间的根本不兼容问题。尝试在没有 ORM 的情况下针对 SQL 数据库编写任何复杂的、可测试的 OO 软件是非常低效的,充其量是。

              良好的 ORM 还提供实践和工具,使创建和维护一致的高质量数据库架构变得更加容易,因此平均而言,使用 ORM 的团队将取得领先。手工架构更像是编写 C++ ......人们可以做到,但在现实世界中,随着时间的推移很难保持质量,以至于普通产品并不好。

              【讨论】:

              • 实际上,可以说不得不求助于合成主键意味着您没有做足够的研究来理解您尝试建模的领域。合成主键没有说明您正在建模的实体,它只是一个数字。复合键更好,因为它与域相关联。
              • “复合键...通常表明您的数据模型存在缺陷”——我可以看到它们如何有时表明存在缺陷,但“通常“?不。考虑到您可能在物理数据模型中使用 using 代理键来“隐藏”逻辑数据模式中存在的复合键。
              • @Machine:我认为将密钥绑定到域是有风险的,因为人类用作标识符的任何数据位都可能会更改格式。同样,您的应用程序可能可以正常运行数年,然后有人决定更改工资单数字的工作方式。我不得不处理这样的情况。
              • @onedaywhen:我只能根据经验说话,所以 YMMV,但我的说法与 Machine 所描述的相反——每次有人说“让我们使用复合键”时,通常意味着有一个他们不想花时间解决的复杂问题。我只能想到一个例子,即使在所有假设之后,使用复合仍然有意义,但这现在有点可疑,因为某些项目有一组备用标识符。
              • 我看到你的内容主要是关于 Ruby on Rails。老实说,在阅读“每种语言都有一个事实上的 ORM”之后,我感到很惊讶。 .Net 没有。 Java 没有。 C/C++ 没有。 PHP 没有(尽管 mysql 包可能是最常见的)。但 Ruby 确实……你在 Ruby 之外有多少经验?
              【解决方案9】:

              就域模型而言,当表不代表实体时创建复合主键没有任何问题 - 即当它代表连接表时(正如您在问题中提到的那样),除了它不是单调递增的,那么在插入过程中会出现一定数量的分页。

              某些 ORM 不能很好地处理复合主键,因此为主键创建代理自动整数并使用非聚集索引覆盖列可能更安全。

              【讨论】:

                【解决方案10】:

                我几乎从未见过复合键是个好主意的情况(例外,连接表仅包含两个代理键)。首先,您在子表中浪费空间。您正在损害连接的性能,因为整数连接通常要快得多。如果您将复合键作为聚集索引(此处讨论 SQL Server),那么您将导致数据库在存储记录方面效率较低,并且在构建其他索引时效率较低 - 所有这些都使用聚集索引。

                当键中的数据发生变化时(几乎不可避免地会发生变化),那么您需要更新所有相关的表,同时避免大量不必要的更新,并在数据库设计为使用 surrogaste 时完全不需要的任务上浪费处理能力键。主键不仅需要是唯一的,而且是不变的。复合键通常无法通过第二次测试。

                因此,您正在考虑使用一种会损害性能、导致内存和数据库存储使用不佳、在子记录中使用更多空间(另一种资源浪费)并且需要痛苦地更新可能是数百万子记录的技术事情会改变的。这可能会使使用 ORM 变得困难?为什么要这么做?因为你懒得放一个代理键,然后在潜在的复合键上定义一个唯一索引?使用综合指数有什么好处吗?由于缺少 5 分钟的工作,您正在永久性地损害您的数据库?

                【讨论】:

                • 不确定是否浪费空间。组合 PK 中的值进入索引,通常是树。数据库中的每个值将只有一个实例作为树中的一个节点。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-12-22
                • 1970-01-01
                • 1970-01-01
                • 2010-10-10
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多