为啥复合主键还在?
Posted
技术标签:
【中文标题】为啥复合主键还在?【英文标题】:Why are composite primary keys still around?为什么复合主键还在? 【发布时间】:2011-07-21 07:59:57 【问题描述】:我被分配将数据库迁移到中级 ERP。 新系统到处使用复合主键,从实用的角度来看,为什么?
与自动生成的 ID 相比,我只能看到负面的方面;
外键变得模糊 更难的迁移或数据库重新设计 随着业务变化不灵活。 (我的车没有 reg.plate..) 通过约束更好地实现相同的完整性。又回到了候选键的设计理念,我没看出来。
这是软盘时代的习惯/神器(最小化空间/索引),还是我错过了什么?
//编辑// 刚刚找到好的 SO-post:Composite primary keys versus unique object ID field //
【问题讨论】:
@dportas:候选键的意义何在? 强制候选键的重点是通过防止重复数据进入系统来确保需要唯一的属性的唯一性。指定和使用候选键的重点是:提供标识符,用户可以通过这些标识符正确识别他们感兴趣的数据;帮助确保数据准确地对应于话语领域;执行业务规则。这些显然是相关且互补的原因,它们共同意味着基本相同的事情:数据完整性。 您可以强制一列是唯一的,而不必将其绑定到主键。按照问题中的建议将主键保留为自动生成的 ID,并确保需要具有唯一值的列执行该规则。 @dportas:你确定你理解正确的候选键是什么? @JNK:屈尊于$person
关于$subject
可能不会让$condescendent
走得太远(无论相关知识水平如何)
【参考方案1】:
当你的主键是非代理的并且本质上是复合的,也就是说,可以分解成几个不相关的部分时,复合键是必需的。
一些现实世界的例子:
多对多链接表,其中主键由相关实体的键组成。
当tenant_id
是每个实体的主键的一部分并且实体只能在同一租户内链接(受外键约束)时的多租户应用程序。
处理第三方数据的应用程序(已提供主键)
请注意,从逻辑上讲,所有这些都可以使用UNIQUE
约束来实现(除了代理PRIMARY KEY
)。
但是,有一些实现特定的东西:
某些系统不允许FOREIGN KEY
引用不是PRIMARY KEY
的任何内容。
某些系统只会将表聚集在 PRIMARY KEY
上,因此将组合设为 PRIMARY KEY
将提高在组合上加入查询的性能。
【讨论】:
@quassnoi,哪些系统不会让 FOREIGN KEY 约束引用除 PRIMARY KEY 约束之外的任何内容?哪些系统只会聚集在 PRIMARY KEY 约束上?我不知道有任何 DBMS 有这样的限制。 @Quassnoi - 很好的答案。您能否详细解释一下多租户情况是如何运作的? @dportas:Paradox
不允许引用 PRIMARY KEY
以外的任何内容; mysql
与 InnoDB
和 Oracle
仅在 PRIMARY KEY
上聚集表(请注意,Oracle
将其称为 索引组织表,而它所称的 @987654335 则意味着完全不同的东西@)
@orokusaki:如果您有多租户 posts
和 tags
,而 tenant_id
不是可引用键的一部分(PRIMARY
或 UNIQUE
),则输入posts_tags
可以引用来自不同租户的post
和tag
。要正确处理它,您应该将 tenant_id
作为两个表中键的一部分,将其添加到 posts_tags
并使其成为适当表的两个 FOREIGN KEY
约束的一部分。
@Quassnoi - 啊,太好了,谢谢。 Django(我使用)不提供复合主键支持。你有这方面的 Python 经验吗?我很乐意使用它而不是我必须使用的所有样板代码来保证租户 A 永远无法访问租户 B 的行(这感觉不对,而且肯定会增加开销)。【参考方案2】:
我个人更喜欢使用代理键。但是,在连接仅包含来自其他两个表的 id 的表(以创建多对多关系)时,复合键是可行的方法,因此将它们取出会使事情变得更加困难。
有一种观点认为代理键总是不好的,如果您没有通过使用自然键来记录的唯一性,那么您的设计就很糟糕。我强烈不同意这一点(如果您不存储 SSN 或其他一些唯一值,例如,我不同意您为 person 表想出一个自然键。)但许多人认为有必要进行适当的规范化。
有时使用复合键可以减少连接到另一个表的需要。有时它不会。因此,有时复合键可以提高性能,也有时会损害性能。如果键相对稳定,则选择查询的性能更快可能没问题。但是,如果它是像公司名称这样可能会更改的东西,那么当 A 公司更改其名称并且您必须更新一百万条相关记录时,您可能会受到伤害。
在数据库设计中没有一种适合所有人的方式。有时复合键很有用,有时也很可怕。代理键有时有用,有时无用。
【讨论】:
非常重要的提示: 社会安全号码不是唯一的,因此不是一个好的候选者。 blogs.computerworld.com/node/5969 @BoffinbraiN:候选键不能是好或坏,它可以存在也可以不存在。如果一个表中有两个或多个相同的SSN
,则SSN
不是候选键,如果不是,则它是。
明白。我会更清楚一点:即使您的表不包含重复的 SSN,它仍然绝不能被视为候选者,否则您将来可能会受到惩罚(如文章中所述)。
@BoffinbraiN:某个东西是否是候选键不是在同一个表中存在两个相同的值(或元组)(这意味着您在数据已经完成之后进行数据库设计那里!),而是可能的数据集(为了讨论,我们会说它是所有人)是否会产生重复值。在这方面,因为不能保证 SSN 在所有可能的人中都是唯一的,所以 SSN 不是候选键。
同意,+1 以获得平衡的答案。反模式没有使用代理键,也没有使用复合键。反模式在每种情况下都盲目地使用一种或另一种解决方案。【参考方案3】:
复合主键提供更好的性能当涉及到它们被用作其他表中的外键并减少表读取时 - 有时它们可以成为救生员。如果您使用代理键,则必须转到该表以获取自然键信息。
例如(纯示例 - 所以我们不是在这里讨论 DB 设计),假设您有一个 ORDER
表和 ORDER_ITEM
。如果您在ORDER_ITEM
中使用ProductId
和LineNumber
(UPDATE: 以及Pedro 提到的OrderId
甚至更好的OrderNumber
)作为复合主键,那么在您的交叉表中为@ 987654329@,您将能够在SHIPPING_ORDERITEM
中拥有 ProductId。这可以极大地提高您的性能,例如,如果您已经用完该产品并需要找出该 ProductId
的所有产品,这些产品需要在无需加入的情况下发货。
另一方面,如果您使用代理键,则必须加入,并且最终会得到一个非常低效的 SQL 执行计划,它必须在多个索引上执行 书签查找。 p>
请参阅 书签查找 上的 more,其中使用代理键成为主要问题。
【讨论】:
ProductId 和 LineNumber 在哪个表中是复合键? 那么,如果您再订购同一产品,您会怎么做? 当然你也会有 OrderId,正如我所说的,我不是在谈论数据库设计。但谢谢,我也会添加 OrderId。 我将 productid 非规范化并放入 shipping_orderitem 中。这难道不是更快,而且产品表中的历史也能幸存下来吗?【参考方案4】:自然主键很脆弱。
假设我们已经围绕(CountryCode、PhoneNumber)上的自然 PK 构建了一个系统,并且几年后我们需要添加 Extension,或者将 PK 更改为一列:Email。如果将这些 PK 列传播到所有子表,这将变得非常昂贵。
几年前,有些系统假设社会安全号码是一个自然的 PK,当 SSN 变得非唯一且可以为空时,必须重新设计以使用身份。
因为我们无法预测未来,我们不知道以后的一些变化是否会使过去完全正确和完整的模型变得过时。
【讨论】:
基于不正确或不完整建模的自然键很脆弱。 FTFY。 任何依赖于我们理解“正确”或“完整”的方法都是脆弱的。 FTFY。 您的论点基本上是自然键,实际上,自然键不是好的自然键。为了胜利而重言式? @jdmichal 长期是的,“自然键不是好的自然键” (续)如果 ISBN 重复是可能的,或者如果图书的卖家或出版商采取了便宜的路线,只是分配了一个 ISBN,而不是从提供他们的注册机构购买一个 ISBN出去。如果有可能,则 ISBN 不是候选键,因为可能存在重复项。如果是出版商走廉价路线,那么 ISBN 不是候选键,因为并非所有书籍都有它们。无论哪种方式,特定候选密钥的失败并不意味着自然密钥天生就不好,只是需要进行研究和尽职调查以确保它们是合法的。【参考方案5】:非常简单的答案是数据完整性。如果数据要有用且准确,则可能需要密钥。拥有“自动生成的 id”也不会消除对其他键的要求。另一种选择是不强制唯一性并接受数据将被复制并且几乎不可避免地包含异常并因此导致错误。你为什么想要那个?
【讨论】:
您可以强制唯一性而不将其设为主键。如果您有一个自然键和一个代理键,那么在自然键上不添加唯一索引将是糟糕的设计。 @HLGEM,一个键就是一个键。 “主”键和任何其他候选键之间没有区别。索引与它没有多大关系。键由 约束 强制执行,而索引只是加快数据访问速度的一种方式。 如果投反对票的人能解释原因,我将不胜感激。【参考方案6】:简而言之,组合键的目的是使用数据库来执行一个或多个业务规则。换句话说:保护数据的完整性。
例如。您有一份从供应商处购买的零件清单。您可以像这样创建供应商和零件表:
SUPPLIER
SupplierId
SupplierName
PART
PartId
PartName
SupplierId
哦哦。零件表允许重复数据。由于您使用了自动生成的代理键,因此您并没有强制执行供应商的零件只能输入一次的事实。相反,您应该像这样创建 PART 表:
PART
SupplierId
SupplierPartId
PartName
在此示例中,您的零件来自特定供应商,您希望在 PARTS 表中强制执行以下规则:“单个供应商只能提供一个零件一次”。因此,复合键。您的复合键可防止意外重复输入零件。
您始终可以将业务规则留在数据库之外并留给应用程序,但通过将规则保留在数据库中(通过组合键),您可以确保业务规则在任何地方都得到执行,尤其是在您应该执行的情况下决定允许多个应用程序访问数据。
【讨论】:
这是一个很好的例子,但它忽略了在非主键字段上构建约束的能力。您是否发现保留原始结构并在PartId + SupplierId
上定义唯一约束存在问题?【参考方案7】:
正如函数封装一组指令或数据库视图抽象基表连接一样,代理键也抽象了它们所在实体的含义。
例如,如果您有一个包含车辆数据的表,则应用替代 VehicleId 从数据的角度抽象出车辆的含义。当您提到 VehicleId = 1 时,您肯定是在谈论某种车辆,但我们知道它是 2008 年的雪佛兰 Impala 还是 1991 年的福特 F-150?不可以。任何车辆#1 的基础数据可以随时更改吗?是的。
【讨论】:
这是对键的解释,而不是对代理键的解释。 我不同意。自然键实际上代表了实体的具体细节。代理键抽象了这一点。也许我没有抓住重点。【参考方案8】:简答:多列外键自然是指多列主键。仍然可以有一个自动生成的 id 列作为主键的一部分。
哲学答案:主键是行的标识。如果有一些信息是行标识的固有部分(例如文章属于哪个客户......在多客户 wiki 中) - 该信息应该是主键的一部分。
一个例子:组织局域网聚会的系统
该系统支持多个 LAN 聚会,有相同的人和组织者参加,因此:
CREATE TABLE users ( users_id serial PRIMARY KEY, ... );
还有几方:
CREATE TABLE parties ( parties_id serial PRIMARY KEY, ... );
但大多数其他东西需要携带有关它链接到哪一方的信息:
CREATE TABLE ticket_types (
ticket_types_id serial,
parties_id integer REFERENCES parties,
name text,
....
PRIMARY KEY(ticket_types_id, parties_id)
);
...这是因为我们想要引用主键。 attendances 表上的外键指向 ticket_types 表。
CREATE TABLE attendances (
attendances_id serial,
parties_id integer REFERENCES parties,
ticket_types_id integer,
PRIMARY KEY (attendances_id, parties_id),
FOREIGN KEY (ticket_types_id, parties_id) REFERENCES parties
);
【讨论】:
我想我们都清楚什么是复合键是,OP在问为什么人们仍然使用它们。 @Adam:为什么不呢?从数据完整性的角度来看,它们在某些情况下是必要的。该示例试图证明它与自动生成与复合无关。无论如何都需要复合主键。【参考方案9】:虽然我更喜欢代理键,但我在少数情况下使用复合案例。复合键可以全部或部分由代理键字段组成。
多对多连接表。无论如何,这些通常都需要密钥对上的唯一密钥。在某些情况下,键中可能会包含其他列。 弱子表。订单行之类的东西并不是独立存在的。在这种情况下,我使用复合表中的父(订单)表主键。当有多个弱表与一个实体相关时,查询子数据时有可能将一个表从连接集中剔除。在孙表的情况下,可以在不涉及中间表的情况下将祖父母加入到孙表中。
【讨论】:
以上是关于为啥复合主键还在?的主要内容,如果未能解决你的问题,请参考以下文章