复合主键:好还是坏?
Posted
技术标签:
【中文标题】复合主键:好还是坏?【英文标题】:Composite Primary Keys : Good or Bad? 【发布时间】:2014-11-22 14:02:16 【问题描述】:虽然可以使用复合主键,但对于下面的情况,这真的是一种不好的做法吗? Stackoveflow 上的共识在这个问题上似乎是双向的。
为什么?
我想将订单的付款存储在单独的表中。原因是,一个订单可以有许多项目,这些项目以多对多关系的形式在单独的表中处理。现在,如果我的付款表不使用复合主键,我将失去唯一的PaymentID
:
[PaymentId] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
[OrderId] INT NOT NULL PRIMARY KEY --Also a Foreign Key--
现在,如果我只删除 OrderId
的主键,我将在这里失去我的一对一关系,所以 Many OrderIds can be associated to many PaymentIds
,我不想要这个。
这似乎就是为什么其他关于 SO 的答案(大部分)得出的结论是复合键是一个坏主意。如果不好,那么最佳做法是什么?
【问题讨论】:
如果我理解正确的话,在这种情况下,您可以在OrderId
上添加一个单独的唯一约束,并将PaymentId
作为主键。
我没看懂:“原因是,一个订单可以有很多项目,这些项目也可以在一个单独的表中以多对多关系的形式处理。”?如果您在payments
表中有order_id
,那么您所要做的就是用orders
表引用它,您将如何失去唯一的PaymentID
?
@Laurence:是的,但是在这种情况下,1 个订单可以有多次付款,这很糟糕,对吗?
在我看来,为一个订单多次付款一点也不差。
@Surya:请看我之前的评论
【参考方案1】:
没有复合主键不好的结论。
最佳做法是使用 一些 列来唯一标识一行。但在某些表中,单列本身不足以唯一标识一行。
SQL(和关系模型)允许使用复合主键。在某些情况下,这是一个很好的做法。或者,另一种看待它的方式是,在所有情况下这都不是坏习惯。
有些人认为每个表都应该有一个整数列来自动生成唯一值,并且应该作为主键。有些人还声称这个主键列应该始终称为id
。但这些都是惯例,不一定是最佳实践。约定有一些好处,因为它简化了某些决定。但约定也是有限制的。
您的订单可能需要多次付款,因为有些人购买了on layaway,或者他们有多种付款来源(例如两张信用卡),或者两个不同的人想为订单的一部分付款(我经常和朋友一起去餐馆,我们每个人都为自己的饭菜买单,所以工作人员用我们每张信用卡处理一半的订单)。
我会设计你描述的系统如下:
Products : product_id (PK)
Orders : order_id (PK)
LineItems : product_id is (FK) to Products
order_id is (FK) to Orders
(product_id, order_id) is (PK)
Payments : order_id (FK)
payment_id - ordinal for each order_id
(order_id, payment_id) is (PK)
这也和identifying relationship的概念有关。如果仅因为订单存在而定义付款存在,则将订单作为主键的一部分。
请注意,LineItems 表也缺少自己的自动增量单列主键。多对多表是一个很好地使用复合主键的经典示例。
【讨论】:
“在所有情况下这都不是一个坏习惯”。 . .我可以同意。 一些反对声明复合主键的观点似乎是由一些 ORM 工具的工作方式驱动的。皮埃尔可能会也可能不会处于这种情况。 @WalterMitty,对,像 Ruby on Rails 之类的 ORM 框架一开始只有关于 PK 设计的“有意见的软件”这个短语是id
,但这就像说你不支持使用不止一个论点。在第一个版本之后,RoR 支持复合主键。所有框架最终都会得出相同的结论。如果有人还在使用不支持复合 PK 的 ORM,则需要升级。
还值得指出的是,自动增量保证了表行的唯一性,但不一定保证每个主题实体的单一身份。操作错误可能导致重复输入同一个人、课程、产品等。
@nickdnk 无论如何,你说得对,按顺序插入是有好处的。有关一些解释和巧妙的图形证明,请参阅此博客:percona.com/blog/2015/04/03/…【参考方案2】:
这个问题很危险地接近征求意见,这可能引发宗教战争。作为一个高度倾向于在我的表中使用自动递增整数主键(称为TablenameId
,而不是Id
)的人,在一种情况下它是可选的。
我认为其他答案解决了您需要主键的原因。
一个非常重要的原因是为了参考。在关系数据库中,理论上任何实体都可以通过外键关系被另一个实体引用。对于外键,您肯定希望一列唯一地定义一行。否则,您必须处理彼此对齐的不同表中的多个列。这是可能的,但很麻烦。
您所指的表不是“实体”表,而是“联结”表。它是一种用于处理多对多关系的关系数据库结构。因为它并不真正代表一个实体,所以它不应该有外键关系。因此,复合主键是合理的。在某些情况下,例如当您担心数据库大小时,甚至需要省略人工主键。
【讨论】:
如果您能回答@philipxy 的评论,那将很有帮助。因为我目前正处于考虑权衡的设计阶段。 @AnishRamaswamy 我认为他的意思是,如果您想将两个表链接在一起,您可以通过唯一标识符链接它们。另一个表的主键成为您表中的外键。他说他的偏好是该主键不是复合键,因为他可能不希望将多个列导入到他的表中,而他可能只想要一个。 我认为对这个旧答案发表评论以添加重要评论会有所帮助:TableNameId
令人讨厌地多余。【参考方案3】:
磁盘空间很便宜,因此在以约定命名的 int identity(1,1) 上聚集的主键(如 pk + 表名)是一个好习惯。它将使查询、连接、索引和其他约束易于管理。
但是有一个很好的理由不这样做(至少在 MS SQL Server 中):如果您想在底层存储系统中管理数据的物理排序。
聚集的主键决定了物理排序顺序。如果您在标识列上执行此操作,则物理排序顺序基本上是插入顺序。但是,这可能不是最好的,特别是如果您总是以相同的方式查询表。 在非常大的表上,获得正确的物理排序顺序会使查询速度更快。例如,您可能希望在两列的组合上使用聚集索引。
【讨论】:
【参考方案4】:最佳做法充其量是有帮助的,但最坏的情况是盲目的。违背最佳实践并不是一种罪过。只要确保你知道你在做什么样的权衡。
数据库引擎可能是非常复杂的东西。在不知道给定引擎进行了哪些特定优化的情况下,很难确定哪种构造会产生最佳性能(因为我假设我们在这里讨论的问题是性能)。复合键可能对一种数据库中的大表有问题,但对另一种数据库没有任何明显影响。
我学到的一个有用的做法是始终努力让我的应用程序尽可能简单。使用复合键是否可以让您不必在插入之前执行查找或其他一些麻烦事?使用它们。但是,如果您注意到使用它们会使您的应用程序不再满足某些重要的性能要求,请考虑不使用它们的解决方案。
【讨论】:
【参考方案5】:如果您的具有复合主键的表预计会有数百万行,则控制复合键的索引可能会增长到 CRUD 操作性能非常下降的程度。在这种情况下,最好使用简单的整数 ID 主键,其索引将足够紧凑,并建立必要的 DBE 约束以保持唯一性。
来源:
https://www.toptal.com/database/database-design-bad-practices
【讨论】:
以上是关于复合主键:好还是坏?的主要内容,如果未能解决你的问题,请参考以下文章