ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

任何人都可以提供意见或增加这个话题的深度吗?

有问题的数据库/表的大小是多少?还有,什么平台?
平台是甲骨文。现在的大小为零,这是一个最近构建并正在测试的模式。
坦率地说,我很惊讶这个问题没有被凿沉并转移到讨论板上。这就是这个,一个讨论,而不是可以简单地回答的东西。

P
PhD

大多数常用引擎(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 引擎无法有效实现标准 SQL 结构(如 DOMAIN)的问题。当属性由 DOMAIN 语句定义时,可以使用 ALTER DOMAIN 语句在模式范围内执行对属性的更改。不同的 SQL 引擎在更改域时具有不同的性能特征,并且一些 SQL 引擎根本不实现域,因此数据建模者通过添加代理键来弥补这些情况,以提高对属性进行更改的能力。

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


证据是代理键的4个理由;自然的3个原因。你说“我倾向于自然键(显然)”不要遵循其中的“明显”部分。
我只是为两者提供了一些示例参数;我给出的论点的数量不应被解释为表明我的倾向。 “显然”部分来自这样一个事实,即我为每个代理键参数提供了相反的参数,但没有为自然键参数。
反对自然键的主要/最引人注目的论点是它们可以改变!此外,很难相信性能不会受到替代品的多段自然键(例如零件号 + 供应商帐户 + 客户帐户 --> 折扣)的影响......
性能受 SQL 引擎处理密钥的设计方式的影响。在优化良好的引擎中,键值和元数据的更新很快,因为底层引擎实际上并没有冗余地存储信息。
由于您在 (2) 中概述的原因,我一直是复合键的粉丝 - 特别是当我不可避免地必须运行一次性查询或数据更新时!自然键还可以帮助人类扫描和维护数据,这在测试/QA 方面可不是小菜一碟。
D
Darrel Miller

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

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


只有 15 年,但我发现了许多情况下自然键是更好的选择。但是我不会仅仅因为我不同意就对你投反对票; )
如何使用相同数据的版本。 product_id, version.. 会有产品/版本的限制。
@daveatflow,每当您使用代理键时,您都需要添加唯一约束(我反对 SK 的论点之一)。
不是说你错了,但是开发数据库应用程序 15 年并不意味着你在这段时间里改进了你的风格,不要把它作为论据,只是像 JeremyDWill 那样提供很好的例子。我和 John Nilson 一起讨论这个问题。
@JamesB 这实际上不是真的。我经常遇到无法保证任何字段唯一的情况,甚至整个记录的唯一约束也不合适。如果不使用代理键,您将如何处理这些事情?
S
Steven A. Lowe

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

如果密钥不是恒定的,那么您未来的更新问题可能会变得非常复杂

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

举一个简单的常见示例:库存物品表。将商品编号(sku 编号、条形码、零件代码或其他任何内容)作为主键可能很诱人,但一年后,所有商品编号都发生了变化,您将面临一个非常混乱的更新——整个——数据库问题...

编辑:还有一个比哲学更实际的问题。在许多情况下,您会以某种方式找到特定的行,然后更新它或再次找到它(或两者兼而有之)。使用复合键,可以在 WHERE 子句中跟踪更多数据,并为重新查找或更新(或删除)提供更多约束。与此同时,关键部分之一也可能发生了变化!使用代理键,始终只保留一个值(代理 ID),根据定义,它不能更改,这大大简化了情况。


P
Powerlord

听起来创建数据库的人在自然键与代理键争论的自然键方面。

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

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


您认为人类很少手动连接表格的假设从何而来?我曾在 OLTP 系统上工作过,其中有数千个存储过程,最肯定包含 JOIN,并且最肯定是手动编写和调整的。
虽然这个问题听起来很笼统,但它有特定的标签。与此答案特别相关的是 ruby-on-rails,它严重依赖于 activerecord ORM。 ORM 在较小的商店中使用很多,而使用 ORM,您实际上并不直接处理数据库连接。
很公平。我没有注意到这一点。事实上,我相信我是通过数据库设计标签进入这个问题的,该标签已被删除。无论如何,诸如“人类很少用手加入桌子”之类的笼统声明已经过时了,我不想让它坐在那里。
J
Jonathan Leffler

使用“唯一(对象)ID”字段可以简化连接,但您应该致力于让其他(可能是复合)键仍然唯一——不要放松非空约束并保持唯一约束。

如果 DBMS 不能有效地处理唯一整数,它就有很大的问题。但是,同时使用“唯一(对象)ID”和另一个键确实比仅使用另一个键使用更多空间(用于索引),并且每个插入操作都有两个索引要更新。所以它不是免费赠品——但只要您也保留原始密钥,那么您就可以了。如果你去掉了另一个键,你就破坏了你的系统设计;所有的地狱最终都会崩溃(你可能会也可能不会发现地狱崩溃了)。


M
Michiel de Mare

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

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

每次我创建一个表,我是否会浪费时间

识别它的主键和它的物理特性(类型、大小)每次我想在我的代码中引用它时记住这些特性?向团队中的其他开发者解释我的 PK 选择?

我对所有这些问题的回答是否定的:

在处理人员列表时,我没有时间浪费尝试识别“最佳主键”。我不想记住我的“计算机”表的主键是一个 64 个字符长的字符串(Windows 是否接受这么多字符作为计算机名称?)。我不想向其他开发人员解释我的选择,其中一位最终会说“是的,伙计,但考虑到您必须管理不同域的计算机?这个 64 个字符的字符串是否允许您存储域名 +计算机名称?”。

因此,在过去的五年中,我一直在遵循一个非常基本的规则:每个表(我们称之为“myTable”)都有其第一个名为“id_MyTable”的字段,它属于 uniqueIdentifier 类型。即使此表支持“多对多”关系,例如“ComputerUser”表,其中“id_Computer”和“id_User”的组合形成了一个非常可接受的主键,我更喜欢创建这个“id_ComputerUser”字段作为唯一标识符,只是为了遵守规则。

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

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


您需要识别自然主键并在其列上强制执行唯一性,否则,您最终将在表中出现重复的除代理键行,这很糟糕!
您当然必须通过 DDL 或外部代码来管理此类问题,但这将是规则之外的。请注意,许多“自然”键是计算出来的(发票编号),因此它们已经必须通过代码生成。
另一件经常被忽视的事情是:如果您使用代理键,那么您确实需要对自然键应用唯一约束。这可能是一个性能问题。由于约束无论如何都需要存在,它也可能是主键。这是一个数据建模问题。如果供应商的产品无法使用 REAL 规范化模型正常运行,那么我们应该向供应商施加压力以修复它,而不是尝试使用代理键之类的变通方法来解决它。如果为了方便或支持 ORM 而添加代理键,那就更糟了。
@PittsburghDBA我不同意,因为无论如何都必须存在唯一约束,所以它也可能是主键——如果有多个唯一字段会发生什么?无论如何,代理键比自然键有很多优势,主要是保证它们在唱片的生命周期内永远不会改变。我认为自然键实际上是一个严重的数据建模问题,无论是否方便或 ORM 问题——它们提供唯一性但不提供身份。 (是的,我知道一些 DBA 不相信记录身份。他们大错特错了,恕我直言。)
我不相信自然键,因为这些键和它们的规则经常变化,就像大自然一样。
佚名

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


我还注意到该行业的一个趋势,即新人们都希望使用工具友好的“数据建模”方法,并强调代理键。当我展示正确的技术时,他们中的大多数人都看着我,好像我有 3 个头。大多数时候,在这些情况下,他们甚至不会对自然键设置唯一约束。
@PittsburghDBA 这两者彼此无关。假设是,如果您使用代理键,那么您还将添加唯一约束以强制执行“自然”键。
L
Lorenzo Boccaccia

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

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

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

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


ORM 噩梦评论是不正确的。例如,试试 LLBLGenPro。它不关心您的密钥中有多少列。关键是关键。实体框架在这方面非常薄弱,至少一开始是这样。我会选择“一些 ORM 很蹩脚,无法处理合适的模型”。请注意,这来自 ORM 粉丝。
M
MattC

我将在这里简短而甜蜜:这些天复合主键并不好。如果可以,添加代理任意键并通过唯一约束维护当前键方案。 ORM 很高兴,你很高兴,原始程序员不太高兴,但除非他是你的老板,否则他可以处理它。


好的,有人在没有解释的情况下对此投了反对票。为什么我的推理不正确?
一方面,将 IDENTITY 列作为“代理”的概念有些缺陷。它比其他任何东西都更类似于记录指针。它无法针对模型中的任何内容进行验证,因此从一开始就具有一定的虚假性质。对于大多数 RDBMS 不能很好地处理大型复合键这一事实,这确实是一个 hack。将假数据引入模型是一种解决方法,而不是解决方案。
我不同意。代理的意思是“任命为继任者、代理人或替代自己”,在这种情况下,这正是任意主键对记录的其余部分所做的事情。除此之外,如果大多数 RDBMS 不能很好地使用复合键,那么这如何减少这样的论点,那么任意键是更可取的呢?
更喜欢什么?坚持一个深图。当一个新实体的主键直到插入之后才知道时,需要多少逻辑?这太荒谬了——根据定义,PK 不能是任意整数数据,因为我们的 RDBMS 允许我们单击一个图标。当然,有些平台现在对 SEQUENCE 等进行了预取,但我仍然更喜欢自然键。话虽如此,我当然像其他人一样使用“Id”黑客。我的意思是,从建模的角度来看,当我们这样做时,我们正在做的是将记录/字段的心态放在应该是基于集合的抽象上。
@PittsburghDBA“坚持一个深度图。当一个新实体的主键在插入之后未知时,需要多少逻辑?”有缺陷的推理。即使您使用自然键,您也不会知道在插入之后是否遇到键冲突。处理它的逻辑与处理代理键的逻辑在同一数量级。
R
Richard Harrison

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

让我担心的是选择复合键的推理含糊不清。对任何技术的含糊不清往往表明缺乏理解——也许遵循别人的指导方针,在书或文章中......

单个唯一 ID 没有任何问题 - 事实上,如果您将应用程序连接到数据库服务器,并且您可以选择使用哪个数据库,那么一切都会很好,并且您几乎可以使用您的密钥和还不算太惨。

已经并且将会有很多关于此的文章,因为没有单一的答案。有些方法和方法需要以熟练的方式谨慎应用。

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


D
David Aldridge

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

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

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


M
Mohit Jain

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

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

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


H
Hank Gay

@JeremyDWill

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

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


...并且您的数据将被非规范化。
K
Keith Williams

您可以两者兼得——因为任何大公司的数据库都可能被多个应用程序使用,包括运行一次性查询和数据导入的人类 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 架构师很高兴,并且您的数据库完整性得以保留!


有趣...那么,如果发票有行项目(具有 ProductId、数量、价格等典型属性),您将如何应用这种方法? InvoiceItem 表中的记录如何引用 Invoice 表中的记录,在这种情况下,您如何让每个人都如此高兴?
“因为任何大公司的数据库都可能被多个应用程序使用”——通常情况下最好进行设置,以免出现这种情况。让一个应用程序提供一个数据库接口(独立于实现)并调解所有其他访问是很容易的。这意味着只有一个应用程序正在接触数据库,并且发生冲突的可能性较小。
还有,这有什么意义?您正在添加代理键,但没有将其设为主键。为什么不?这听起来像是两全其美。
X
Xorcist

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

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

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

但这并不能阻止某人这样做......使用单个自动生成的 32 位整数作为其身份的表,预计将在全球范围内存储特定快餐公司的所有交易,将失败,因为一旦它尝试插入它的第 2,147,483,648 笔交易(这是一个完全可行的场景)。

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅