复合主键与唯一对象ID字段

时间:2008-10-01 18:40:33

标签: ruby-on-rails database design-patterns database-design

我继承了一个数据库,其构思是复合键比使用唯一对象ID字段更理想,并且在构建数据库时,单个唯一ID 从不 用作主键。因为我正在为这个数据库构建一个Rails前端,所以我遇到了使它符合Rails约定的困难(虽然可以使用自定义视图和一些额外的gem来处理复合键)。

编写它的人的这种特定模式设计背后的原因与数据库如何以非有效方式处理ID字段有关,并且当它构建索引时,树类排序存在缺陷。这个解释没有任何深度,我仍然试图围绕这个概念(我熟悉使用复合键,但不是100%的时间)。

任何人都可以提供意见或为此主题添加更深入的内容吗?

15 个答案:

答案 0 :(得分:88)

答案 1 :(得分:32)

我已经开发了15年的数据库应用程序,而且我还没有遇到过一个非代理密钥比代理密钥更好的选择。

我并不是说这种情况不存在,我只是说当你考虑实际开发访问数据库的应用程序的实际问题时,通常代理键的好处开始压倒理论非代理键的纯度。

答案 2 :(得分:22)

主键应常量且无意义;非代理键通常会失败一个或两个要求,最终

  • 如果密钥不是常量,则您的未来更新问题会变得非常复杂

  • 如果密钥没有意义,那么它更有可能改变,即不是恒定的;见上文

采用一个简单的常见示例:库存项目表。将项目编号(sku编号,条形码,零件代码或其他)作为主键可能很诱人,但一年之后所有项目编号都会更改并且您将留下一个非常好的项目编号。凌乱的更新 - 整个数据库问题...

编辑:还有一个比哲学更实际的问题。在许多情况下,你会以某种方式找到一个特定的行,然后再更新它或再次找到它(或两者)。使用复合键,有更多数据可以跟踪WHERE子句中的更多数据以及重新查找或更新(或删除)。在此期间,其中一个关键部分也可能已发生变化!使用代理键,始终只保留一个值(代理ID),根据定义,它不能更改,这可以显着简化情况。

答案 3 :(得分:11)

声音就像创建数据库的人在伟大的自然键与代理键辩论的自然键侧一样。

我从未听说过ID字段上的btree有任何问题,但我也没有深入研究它...

我落在代理键侧:使用代理键时重复次数较少,因为您只在其他表中重复单个值。由于人类很少手工加入桌子,我们不在乎它是否是一个数字。此外,由于索引中只能查找一个固定大小的列,因此可以安全地假设代理的主键查找时间也更快。

答案 4 :(得分:5)

使用'unique(object)ID'字段简化了连接,但是您应该将另一个(可能是复合的)键保持唯一 - 不要放松非空约束并且DO保持唯一约束。

如果DBMS无法有效处理唯一整数,则存在很大问题。但是,同时使用“唯一(对象)ID”和另一个键确实比其他键使用更多空间(对于索引),并且在每个插入操作上都有两个索引要更新。所以它不是免费赠品 - 但只要你保持原来的钥匙,那么你就行了。如果你取消了另一把钥匙,你就会破坏你的系统设计;所有的地狱最终都会破裂(你可能会或可能不会发现地狱破裂了。)

答案 5 :(得分:5)

我基本上是代理关键团队的成员,即使我欣赏和理解JeremyDWill在这里提出的论点,我仍然在寻找“自然”关键比代理更好的情况......

处理此问题的其他帖子通常涉及关系数据库理论和数据库性能。另一个有趣的论点,在这种情况下总是被遗忘,与表格规范化代码生产力有关:

每次我创建一张桌子,我都应该     失去时间

  1. 识别其主键及其主键 物理特征(类型, 大小)
  2. 记住这些特征 每次我想引用它 我的代码?
  3. 向其他人解释我的PK选择 团队中的开发人员?
  4. 我对所有这些问题的回答都是否定的:

    1. 我没有时间失去尝试 确定“最佳主键”时 处理一份人员名单。
    2. 我不想记得那个 我的“computer”表的主键 是一个64个字符长的字符串(确实如此 Windows接受许多字符 对于计算机名称?)。
    3. 我不想解释我的选择 其他开发者,其中一个 最后会说“是的,但是 考虑你必须管理 不同域名的计算机? 这64个字符的字符串是否允许 你存储域名+ 电脑名称?“。
    4. 所以我过去五年一直在用一个非常基本的规则工作:每个表(我们称之为'myTable')的第一个字段叫做“id_MyTable”,它是唯一的标识符类型。即使此表支持“多对多”关系,例如“ComputerUser”表格,其中“id_Computer”和“id_User”的组合形成了非常可接受的关系主键,我更喜欢创建这个'id_ComputerUser'字段作为uniqueIdentifier,只是为了坚持规则。

      主要优点是您不必关心代码中主键和/或外键的使用。获得表名后,就会知道PK名称和类型。一旦知道数据模型中实现了哪些链接,就会知道表中可用外键的名称。

      我不确定我的规则是最好的。但这是一个非常有效的!

答案 6 :(得分:4)

开发新体系结构的实用方法是利用表格的代理键,其中包含数千个多列高度唯一的记录和用于简短描述表的复合键。我经常发现大学要求使用代理键,而现实世界的程序员更喜欢复合键。您确实需要将正确类型的主键应用于表 - 而不仅仅是这种方式。

答案 7 :(得分:3)

使用自然键会使用任何自动ORM作为持久层来制造噩梦。此外,多列上的外键往往相互重叠,这在以OO方式导航和更新关系时会产生进一步的问题。

你仍然可以在一个独特的约束中转换自然键并添加一个自动生成的id;这不会消除外键的问题,但是必须手动更改;希望多列和重叠约束将成为所有关系中的一小部分,因此您可以专注于重构最重要的位置。

自然pk有他们的动机和用法场景并且不是坏事(tm),他们只是倾向于与ORM相处不好。

我的感觉是,与任何其他概念一样,自然键和表格规范化应该在明智而不是盲目的设计约束时使用

答案 8 :(得分:3)

我在这里会变得简短和甜蜜:复合主键现在并不好。如果可以,则添加代理任意键,并通过唯一约束维护当前密钥方案。 ORM很高兴,你很高兴,原来的程序员并不那么开心,但除非他是你的老板,否则他就可以处理它。

答案 9 :(得分:2)

复合键可能很好 - 它们可能会影响性能 - 但它们不是唯一的答案,就像唯一的(代理)键不是唯一的答案一样。

我担心的是选择复合键的推理含糊不清。关于任何技术的模糊性往往表明缺乏理解 - 可能遵循别人的指导方针,在书籍或文章中......

单个唯一ID没有任何问题 - 事实上如果你有一个连接到数据库服务器的应用程序,你可以选择你正在使用它的数据库一切都很好,你几乎可以做任何事情你的钥匙并没有真正受到太大的影响。

有很多关于此的文章,因为没有单一的答案。有些方法和方法需要以熟练的方式仔细应用。

我在数据库自动提供ID方面遇到了很多问题 - 我尽可能避免使用它们,但偶尔也会使用它们。

答案 10 :(得分:2)

  

......数据库如何以非有效的方式处理ID字段,当它构建索引时,树类排序存在缺陷......

这几乎肯定是无意义的,但是当从不同会话以高速率将递增数字分配给PK时,可能与索引块争用的问题有关。如果是这样,那么REVERSE KEY索引可以提供帮助,尽管由于块分割算法的改变而导致索引尺寸更大。 http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/schema.htm#sthref998

Go综合,特别是如果它有助于您的工具集更快速的开发。

答案 11 :(得分:2)

我不是一个经验丰富的人,但我仍然赞成使用主键作为id这里是使用示例的解释..

外部数据的格式可能会随时间而变化。例如,您可能会认为书籍的ISBN会成为书籍中的一个好主键。毕竟,ISBN是独一无二的。但正如这本特别的书正在撰写中一样,美国的出版业正在准备一项重大变革,因为所有ISBN都会增加额外的数字。 如果我们使用ISBN作为书籍表中的主键,我们必须更新每一行以反映这一变化。但后来我们又遇到了另一个问题。数据库中还有其他表通过主键引用books表中的行。除非我们首先浏览并更新所有这些引用,否则我们无法更改books表中的键。这将涉及删除外键约束,更新表,更新books表,最后重新建立约束。总而言之,这是一种痛苦。 如果我们使用自己的内部值作为主键,问题就会消失。没有第三方可以随意告诉我们改变我们的架构 - 我们控制自己的键空间。如果ISBN等内容确实需要更改,则可以在不影响数据库中任何现有关系的情况下进行更改。实际上,我们已经将行的编织与这些行中数据的外部表示解耦在一起。

虽然解释相当书卷但我认为它以更简单的方式解释了事情。

答案 12 :(得分:1)

@JeremyDWill

感谢您为辩论提供一些急需的平衡。特别感谢DOMAIN s。

上的信息

为了保持一致性,我实际上在系统范围内使用代理键,但涉及权衡。我使用代理键诅咒的最常见原因是当我有一个带有规范值的简短列表的查找表时 - 我会使用更少的空间,如果我刚刚创建了值,我的所有查询都会更短/更容易/更快PK而不是必须加入到桌面。

答案 13 :(得分:1)

您可以同时执行这两项操作 - 因为任何大型公司数据库都可能被多个应用程序使用,包括运行一次性查询和数据导入的人工DBA,纯粹为了ORM系统的利益而设计它并不总是实用或可取的。

这些天我倾向于为每个表添加一个“RowID”属性 - 这个字段是一个GUID,每行都是唯一的。这不是主键 - 这是一个自然键(如果可能)。但是,在此数据库之上工作的任何ORM层都可以使用RowID来标识其派生对象。

因此你可能有:

CREATE TABLE dbo.Invoice (
  CustomerId varchar(10),
  CustomerOrderNo varchar(10),
  InvoiceAmount money not null,
  Comments nvarchar(4000),
  RowId uniqueidentifier not null default(newid()),

  primary key(CustomerId, CustomerOrderNo)
)

因此,您的DBA很高兴,您的ORM架构师很高兴,您的数据库完整性得以保留!

答案 14 :(得分:0)

我只是想在这里添加一些我在讨论与关系数据库自动生成的整数标识字段时无法看到的内容(因为我经常看到它们),这就是它的基础类型可以在某个时刻溢出。

现在我并没有试图说这会自动使复合ID成为可能,但事实上即使可以将更多数据逻辑地添加到表中(这仍然是唯一的,单个自动生成的整数身份可以防止这种情况发生。

是的我意识到在大多数情况下它不太可能,并且使用64位整数会给你很大的空间,而且实际上,如果发生这样的溢出,数据库可能应该有不同的设计。

但这并不能阻止某人这样做...一张表使用一个自动生成的32位整数作为它的标识,预计会将所有交易存储在特定的全局级别快餐公司一旦尝试插入2,147,483,648次交易就会失败(这是一个完全可行的方案)。

需要注意的是,人们往往会掩饰或完全忽略。如果要定期插入任何表,则应考虑数据随时间累积的频率和数量,以及是否应使用基于整数的标识符。