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


71

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

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

任何人都可以对此主题发表意见或增加深度吗?


有问题的数据库/表的大小是多少?另外,什么平台?
彼得·迈耶

平台是Oracle。现在的大小为零,它是最近构建并正在测试的架构。
mwilliams

坦白说,令我惊讶的是,这个问题没有被解决,而是进入了讨论区。这就是讨论,不是可以简单回答的问题。
匹兹堡DBA 2012年

Answers:


89

使用代理密钥系统,大多数常用引擎(MS SQL Server,Oracle,DB2,MySQL等)不会遇到明显的问题。有些人甚至可以通过使用代理来提高性能,但是性能问题是特定于平台的。

一般而言,自然键(以及扩展为复合键)的替代键辩论历史悠久,几乎看不到“正确答案”。

自然键(单数或复合键)的参数通常包括以下内容:

1) 它们已经在数据模型中可用。已建模的大多数实体已经包括一个或多个属性或属性组合,这些属性可以满足创建关系所需的键需求。向每个表添加其他属性会合并不必要的冗余。

2) 他们消除了某些联接的需要。例如,如果您的客户具有客户代码,发票具有发票编号(两者都是“自然”键),并且想要检索特定客户代码的所有发票编号,则可以简单地使用"SELECT InvoiceNumber FROM Invoice WHERE CustomerCode = 'XYZ123'"。在经典的代理键方法中,SQL看起来像这样:"SELECT Invoice.InvoiceNumber FROM Invoice INNER JOIN Customer ON Invoice.CustomerID = Customer.CustomerID WHERE Customer.CustomerCode = 'XYZ123'"

3) 它们为数据建模提供了一种更为通用的方法。使用自然键,可以在不同的SQL引擎之间大体上不变地使用相同的设计。许多代理密钥方法使用特定的SQL引擎技术来生成密钥,因此需要对数据模型进行更多专业化处理才能在不同平台上实现。

代理键的争论通常围绕SQL引擎特定的问题展开:

1) 当业务需求/规则更改时,它们使属性更改更容易。这是因为它们允许将数据属性隔离到单个表中。对于没有有效实现标准SQL结构(例如DOMAIN)的SQL引擎,这主要是一个问题。当通过DOMAIN语句定义属性时,可以使用ALTER DOMAIN语句在架构范围内对属性进行更改。不同的SQL引擎对于更改域具有不同的性能特征,并且某些SQL引擎根本不实现DOMAINS,因此数据建模人员通过添加代理键来提高这些属性的能力,从而弥补了这些情况。

2) 与自然键相比,它们使并发的实现更容易。在自然键情况下,如果两个用户同时使用相同的信息集(例如客户行),并且其中一个用户修改了自然键值,则第二个用户的更新将失败,因为他们是客户代码数据库中不再存在更新。在代理键的情况下,更新将成功处理,因为不可变的ID值用于标识数据库中的行,而不是可变的客户代码。但是,并非总是希望允许第二次更新-如果客户代码已更改,则由于该行的实际“身份”已更改,可能不应该允许第二个用户继续进行更改-第二个用户可能更新错误的行。代理键或自然键都无法单独解决此问题。

3) 它们的性能比自然键好。性能最直接受到SQL引擎的影响。由于SQL引擎的数据存储和检索机制,在使用不同SQL引擎的同一硬件上实现的同一数据库架构通常会具有截然不同的性能特征。一些SQL引擎非常接近平面文件系统,当相同的属性(例如客户代码)出现在数据库架构的多个位置时,实际上将冗余存储数据。当需要对数据或架构进行更改时,SQL引擎的这种冗余存储会导致性能问题。其他SQL引擎在数据模型与存储/检索系统之间提供了更好的隔离,从而可以更快地更改数据和架构。

4) 代理键在某些数据访问库和GUI框架中的功能更好。由于大多数代理键设计的同类性质(例如:所有关系键都是整数),数据访问库,ORM和GUI框架可以使用该信息,而无需对数据有特殊的了解。由于自然键的异构性质(不同的数据类型,大小等),自然键在自动或半自动工具箱和库中无法正常工作。对于诸如嵌入式SQL数据库之类的特殊方案,可以在考虑特定工具箱的情况下设计数据库。在其他情况下,数据库是企业信息资源,可以由多个平台,应用程序,报表系统和设备同时访问,因此在着重于任何特定库或框架的情况下,数据库也无法正常运行。此外,

我倾向于倾向于自然键(显然),但是我并不狂热。由于我所处的环境,我帮助设计的任何给定数据库都可能被各种应用程序使用,因此我将自然键用于大多数数据建模,而很少引入替代方法。但是,我不会竭尽全力尝试重新实现使用代理的现有数据库。代理密钥系统可以正常工作-无需更改已经运行良好的功能。

有一些出色的资源讨论了每种方法的优点:

http://www.google.com/search?q=natural+key+surrogate+key

http://www.agiledata.org/essays/keys.html

http://www.informationweek.com/news/software/bi/201806814


5
证据是代理密钥的4个原因。自然的3个原因。您说“(我倾向于倾向于(自然地)使用自然键”)不要遵循其中的“明显”部分。
S.Lott

13
我只是简单地给出了两个例子。我给出的论点数量不应该解释为我的倾向。“显然”部分来自以下事实:我为每个代理关键要素提供了相反的论据,但没有为自然关键论点提供反论据。
JeremyDWill

5
反对自然键的主要/最引人注目的论点是它们可以改变!同样,很难相信性能不会受到替代产品的多细分自然键(例如,零件号+供应商帐户+客户帐户->折扣)的影响
史蒂芬·A·洛

1
性能通过SQL引擎被设计成处理的关键方式的影响。在经过良好优化的引擎中,键值和元数据的更新很快,因为底层引擎实际上并不冗余地存储信息。
JeremyDWill

2
出于您在(2)中概述的原因,我一直是复合键的爱好者-特别是当我不可避免地不得不运行一次性查询或数据更新时!自然键还可以帮助人们扫描和维护数据,这在测试/质量保证方面可谓小菜一碟。
基思·威廉姆斯

33

我从事数据库应用程序开发已有15年了,但我还没有遇到过非代理键比代理键更好的选择的情况。

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


6
只有15年的时间,但是我发现无数情况下自然键是更好的选择。但是我不会因为我不同意而否决你。)
詹姆斯·金

如何处理相同数据的版本。product_id,版本..产品/版本会有限制。
baash05 '04

1
@daveatflow,每当您使用代理键时,都将需要添加唯一约束(我反对SK的论点之一)。
詹姆斯·金

1
不是说您是不对的,而是开发数据库应用程序15年并不意味着您在这段时间里改进了自己的风格,不要将其用作论据,而只是像JeremyDWill那样提供良好的示例。我和约翰·尼尔森在一起。
Leonardo Marques 2013年

@JamesB事实并非如此。我经常碰到那里的情况下没有场可以保证独一无二的,甚至在那里在整个记录的唯一限制是不恰当的。如果不使用代理密钥,您将如何处理这些事情?
Marnen Laibow-Koser

22

主键应该是恒定且无意义的; 非代理密钥通常不能满足一项或两项要求,最终

  • 如果密钥不是恒定的,那么将来会有一个更新问题,可能会变得非常复杂

  • 如果密钥不是无意义的,则它更有可能改变,即不是恒定的;往上看

举一个简单的常见示例:一个清单项目表。将项目编号(SKU编号,条形码,部件代码或其他)作为主键可能很诱人,但是一年后,所有项目编号都发生了变化,您将获得非常混乱的“整个更新”数据库问题...

编辑:还有一个比哲学更实际的问题。在许多情况下,您将以某种方式找到特定的行,然后再对其进行更新或再次查找(或同时查找)。使用复合键,可以在WHERE子句中跟踪和更新更多数据,以进行重新查找或更新(或删除)。同时,其中一个关键段可能也已更改!使用代理密钥,始终只保留一个值(代理ID),并且根据定义,它不能更改,这大大简化了这种情况。


11

听起来好像谁创建的数据库是在伟大的自然键与代理键辩论的自然键侧的人。

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

我属于代理键方面:使用代理键时,您的重复次数较少,因为您仅在其他表中重复了一个值。由于人类很少手动加入桌子,因此我们不在乎它是否是数字。另外,由于在索引中只需要查找一个固定大小的列,因此可以安全地假设代理也通过主键具有更快的查找时间。


2
您认为人类很少手工参加餐桌的假设从何而来?我在OLTP系统上工作,那里有成千上万个存储过程,最确定的是包含JOIN,并且最肯定是手工编写和调整的。
匹兹堡DBA

1
虽然这个问题听起来很笼统,但它具有特定的标签。与这个答案特别相关的是ruby-on-rails,它很大程度上依赖于activerecord ORM。ORM在较小的商店中使用很多,而使用ORM确实不会直接处理数据库联接。
Powerlord 2012年

3
很公平。我没有注意到。实际上,我相信我是通过已删除的数据库设计标签来问这个问题的。无论如何,诸如“人类很少用手加入桌子”之类的笼统声明已经过去了,我不想只是把它放在那儿。
匹兹堡DBA

5

使用“唯一(对象)ID”字段可简化联接,但您应力争使另一个(可能是复合)键仍然唯一-不要放宽非空约束,而要保持唯一约束。

如果DBMS无法有效处理唯一整数,则存在很大的问题。但是,同时使用“唯一(对象)ID”和另一个键确实比另一个键占用更多的空间(用于索引),并且每个插入操作都有两个索引要更新。因此,这不是免费赠品-但只要您也维护原始密钥,就可以了。如果取消其他键,则将破坏系统设计;最终所有地狱都会崩溃(并且您可能会也可能不会发现地狱崩溃了)。


5

我基本上是代理密钥团队的成员,即使我欣赏并理解JeremyDWill此处提出的论点,但我仍在寻找“自然”密钥比代理更好的情况...

有关此问题的其他帖子通常涉及关系数据库理论和数据库性能。在这种情况下始终被遗忘的另一个有趣的参数与表规范化代码生产率有关

每次创建表格时,我都会浪费时间吗

  1. 识别其主键及其物理特性(类型,大小)
  2. 每次想在代码中引用这些特性时,还记得这些特性吗?
  3. 向团队中的其他开发人员解释我的PK选择?

我对所有这些问题的回答都不是:

  1. 与人名单打交道时,我没有浪费时间尝试确定“最佳主键”。
  2. 我不想记住我的“ computer”表的主键是64个字符长的字符串(Windows是否接受这么多字符作为计算机名?)。
  3. 我不想向其他开发人员解释我的选择,他们中的一个最终会说“是的,但是您认为您必须管理不同域上的计算机?这64个字符的字符串是否允许您存储域名+计算机名称?”。

因此,过去五年来,我一直遵循一条非常基本的规则:每个表(我们称其为“ myTable”)都有其第一个字段“ id_MyTable”,该字段具有uniqueIdentifier类型。即使此表支持“多对多”关系,例如“' ComputerUser”表,其中“ id_Computer”和“ id_User”的组合形成了非常可接受的主键,我还是更愿意将此“ id_ComputerUser”字段创建为uniqueIdentifier,只是为了遵守规则。

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

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


4
您需要标识自然主键并在其列上强制唯一性,否则,您将在表中得到重复的代理行除外,这就是错误的!
乔纳森·勒夫勒

当然,您必须通过DDL或外部代码来管理此类问题,但这是该规则的补充。请注意,许多“自然”密钥是经过计算的(发票编号),因此它们已经必须通过代码生成。
菲利普·格隆迪耶

3
经常被忽略的另一件事是:如果使用代理键,则无论如何您确实需要对自然键应用唯一约束。这可能是性能问题。由于约束仍然需要存在,因此它也可能是主键。这是一个数据建模问题。如果供应商的产品不能在REAL归一化模型下正常运行,那么我们应该向供应商施加压力以对其进行修复,而不是尝试使用替代密钥之类的变通办法来解决它。如果添加代理密钥是为了方便或支持ORM,那就更糟了。
匹兹堡DBA 2012年

1
@PittsburghDBA我不同意,因为无论如何都必须有一个唯一约束,所以它也可能是主键-如果有多个唯一字段会发生什么?无论如何,代理键比自然键有很多优点,主要是要保证它们在记录的生命周期内永远不会改变。我认为自然键实际上是一个严重的数据建模问题,而与便利性或ORM问题无关-它们提供唯一性但不提供身份。(是的,我知道有些DBA不相信记录身份。恕我直言,他们做错了。)
Marnen Laibow-Koser 2012年

我不相信自然键,因为这些键及其规则经常像自然一样变化。
2015年

4

开发新体系结构的一种实用方法是利用表的替代键,该键将包含成千上万个多列的高度唯一记录和简短描述表的组合键。我通常会发现,大学决定使用代理键,而现实世界中的程序员更喜欢使用组合键。您确实需要将正确类型的主键应用于表-不仅仅是一种方法。


1
我还注意到了该行业的趋势,即新人们都希望使用工具友好的“数据建模”方法,重点是代理键。当我展示适当的技术时,大多数人都看着我,好像我有3个头。在大多数情况下,在这种情况下,他们甚至没有对自然键施加唯一的约束。
匹兹堡DBA

1
@PittsburghDBA两者彼此无关。假设是,如果您使用代理密钥,那么您还将添加唯一约束以强制执行“自然”密钥。
Zoran Pavlovic

3

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

您仍然可以在唯一约束中转换自然键并添加自动生成的ID;但这并不能消除外键的问题,但是必须手动进行更改。希望多列和重叠约束将是所有关系中的一小部分,因此您可以集中精力在最重要的地方进行重构。

自然的pk有其动机和使用场景,并且不是一件坏事(tm),他们只是往往与ORM融洽相处。

我的感觉是,与其他任何概念一样,在合理的情况下应使用自然键和表规范化,而不应视作盲目的设计约束


2
ORM噩梦评论不正确。例如,尝试LLBLGenPro。不管您的键中有多少列。钥匙就是钥匙。至少在最初,实体框架在这方面非常薄弱。我将继续讨论“某些ORM很la脚,无法​​处理适当的模型”。请注意,这来自ORM粉丝。
匹兹堡DBA

3

在这里,我将简短有趣:组合主键现在已经不好了。如果可以,请添加替代密钥,并通过唯一约束维护当前密钥方案。ORM很高兴,您很高兴,原始程序员不是那么高兴,但是除非他是您的老板,否则他就可以解决。


好的,有人对此一票否决,没有任何解释。为什么我的推理不正确?
MattC 2012年

1
一方面,IDENTITY列作为“代理”的概念有些缺陷。它比其他任何东西更类似于记录指针。它无法针对模型中的任何内容进行验证,因此从一开始就具有一定的虚假性质。实际上,这是一个变通方法,可以解决大多数RDBMS在使用大型组合键时性能不佳的问题。将伪数据引入模型是一种解决方法,而不是解决方案。
匹兹堡DBA 2012年

1
我不同意。代理的意思是“任命自己的继任人,代理人或替代人”,在这种情况下,这正是任意主键在其余记录中所做的工作。除此之外,如果大多数RDBMS在组合键上的表现不佳,那么如何减少参数,然后再使用任意键呢?
MattC

2
更喜欢什么?坚持一个深图。当新实体的主键直到插入后才是未知的,需要多少逻辑?这太荒谬了-根据定义,PK不能是一些任意的整数数据,只是因为我们的RDBMS允许我们单击图标。当然,有些平台现在可以对SEQUENCE等进行预取,但是我仍然更喜欢自然键。话虽如此,我当然也像其他人一样使用“ Id”黑客。我是说,从建模的角度来看,我们要做的就是将记录/字段的思想放在应该基于集合的抽象上。
匹兹堡DBA

2
@PittsburghDBA“坚持一个深图。当一个新实体的主键直到插入后才是未知的时,需要多少逻辑?” 错误的推理。即使您使用自然键,也要等到插入后才知道是否存在键冲突。处理该逻辑的逻辑量级与使用代理密钥进行DEA的逻辑量级相同。
Marnen Laibow-Koser'7

2

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

使我担心的是选择组合键的原因中的模糊性。对任何技术的模糊不清往往表示缺乏理解-可能是在书或文章中遵循别人的指南...。

唯一的ID没什么问题-实际上,如果您已将应用程序连接到数据库服务器,并且可以选择要使用的数据库,那么这一切都会很好,并且您几乎可以使用任何键和并没有遭受太大的痛苦。

关于这个问题,已经有并且将会有很多记载,因为没有单一的答案。有一些方法和方法需要以熟练的方式仔细应用。

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


2

...数据库如何以无效方式处理ID字段,以及在建立索引时,树排序存在缺陷...

几乎可以肯定这是胡说八道,但可能与在不同会话中以较高的速率向PK分配递增编号时的索引块争用问题有关。如果是这样,那么REVERSE KEY索引可以为您提供帮助,尽管由于块分割算法的更改而导致索引大小变大。http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/schema.htm#sthref998

进行合成,特别是如果它有助于您的工具集更快地进行开发。


2

我不是一个经验丰富的人,但仍然支持使用主键作为id,这里是使用示例的说明。

外部数据的格式可能会随时间变化。例如,您可能认为一本书的ISBN将成为一本书的主键。毕竟,ISBN是唯一的。但是随着本书的写作,美国的出版业正为重大变化做准备,因为所有ISBN都增加了数字。如果我们将ISBN用作书籍表中的主键,则必须更新每一行以反映此更改。但是然后我们会遇到另一个问题。数据库中还会有其他表通过主键引用books表中的行。除非先阅读并更新所有这些参考,否则我们无法在books表中更改键。这将涉及删除外键约束,更新表,更新books表以及最终重新建立约束。总而言之,这很痛苦。如果我们使用自己的内部值作为主键,问题就会消失。没有第三方可以随便告诉我们更改架构-我们控制自己的键空间。而且,如果确实需要更改ISBN之类的内容,则可以更改它而不会影响数据库中的任何现有关系。实际上,我们已经将行的编织与这些行中数据的外部表示分离了。

尽管解释是很活泼的,但是我认为它以一种简单的方式解释了事情。


1

@JeremyDWill

感谢您为辩论提供一些急需的平衡。特别感谢您提供有关的信息DOMAIN

为了保持一致性,我实际上在整个系统范围内都使用代理密钥,但是权衡利弊。我使用代理键进行诅咒的最常见原因是当我有一个包含规范值简短列表的查找表时—我将使用较少的空间,并且如果我刚创建了这些值,我的所有查询将更短/更轻松/更快。 PK,而不必加入表格。


...并且您的数据将被规范化。
Marnen Laibow-Koser

1

两者都可以做-因为任何一个大公司数据库都可能会被多个应用程序使用,包括运行一次性查询和数据导入的人工DBA,因此仅出于ORM系统的利益而设计数据库并不总是可行或不理想的。

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

因此,您可能具有:

创建表dbo.Invoice(
  CustomerId varchar(10),
  CustomerOrderNo varchar(10),
  InvoiceAmount money not null,
  评论nvarchar(4000),
  RowId uniqueidentifier不为null default(newid()),

  主键(CustomerId,CustomerOrderNo)
)

因此,您的DBA很高兴,您的ORM架构师也很高兴,并且数据库完整性得到了保留!


有趣的...如果发票包含行项目(具有典型属性,如ProductId,Quantity,Price等),那么您将如何应用此方法?InvoiceItem表中的记录将如何引用Invoice表中的记录,在这种情况下,您将如何使每个人都感到高兴?
Yarik 2011年

“因为任何大型公司数据库都可能被多个应用程序使用” –通常最好进行设置,但事实并非如此。一个应用程序提供一个DB接口(与实现独立)并介导所有其他访问很容易。这意味着只有一个应用程序正在接触数据库,发生冲突的可能性较小。
Marnen Laibow-Koser

还有,这有什么意义呢?您要添加代理键,但不能使其成为主键。为什么不?这听起来像两全其美。
Marnen Laibow-Koser 2013年

0

我只想在此处添加一些东西,这些东西在与关系数据库讨论自动生成的整数标识字段时从未见过(因为我经常看到它们),也就是说,它的基本类型可能会在某个时候溢出。

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

是的,我意识到在大多数情况下这不太可能,并且使用64位整数会为您留出很大的空间,并且实际上,如果发生了这种溢出,则数据库的设计可能应该采用不同的方式。

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

只是要注意一点,人们倾向于掩饰或完全忽略。如果要定期插入任何表,则应考虑随着时间的推移将多久累积一次数据以及累积多少数据,以及是否甚至应使用基于整数的标识符。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.