复合主键

时间:2009-09-05 11:09:21

标签: sql database primary-key composite

我正在设计一个数据库,用于存储源自许多不同来源的数据。我存储的实例由原始源分配唯一ID。我存储的每个实例都应包含有关其来源的信息,以及与此源关联的ID。

作为示例,请考虑下表说明问题:

----------------------------------------------------------------
| source_id | id_on_source | data                              |
----------------------------------------------------------------
| 1         | 17600        | ...                               |
| 1         | 17601        | ...                               |
| 2         | 1            | ...                               |
| 3         | 1            | ...                               |
----------------------------------------------------------------

请注意,尽管id_on_source对于每个来源都是唯一的,但可以为不同的来源找到相同的id_on_source

我对关系数据库有一个很好的理解,但远非专家甚至是有经验的用户。我在设计中遇到的问题是我应该用作主键的问题。数据似乎要求使用(source_id, id_on_source)的复合主键。经过一番谷歌搜索后,我发现了一些关于复合主键优缺点的激烈争论,让我有点困惑。

该表与其他表具有一对多的关系,因此将在其他表的外键中引用。

我与特定的RDBMS无关,我不确定这是否与争论有关,但我要说我更愿意使用SQLiteMySQL

在这种情况下使用复合外键有哪些优缺点?你更喜欢哪一个?

8 个答案:

答案 0 :(得分:28)

我个人认为复合主键很痛苦。对于您希望加入“sources”表的每个表,您需要添加source_id和id_on_source字段。

我会在sources表上创建一个标准的自动递增主键,并在source_id和id_on_source列上添加唯一索引。

然后,这允许您只将sources表的id添加为其他表的外键。

通常我也发现在许多框架和工具产品中对复合主键的支持最多是“不完整”而在其他框架中不存在

答案 1 :(得分:12)

复合键难以管理且加入速度慢。由于您正在构建汇总表,因此请使用代理键(即自动增量/标识列)。留下你的自然键列。

这也有很多其他好处。首先,如果您与公司合并,并且他们拥有相同的来源,但重复使用密钥,那么如果使用代理密钥,您将遇到麻烦。

这是广泛认可的数据仓库最佳实践(比您正在做的更大的事业,但仍然相关),并且有充分的理由。代理提供数据完整性和快速连接。您可以使用自然键快速刻录,因此请远离它们作为标识符,并仅在导入过程中使用它们。

答案 2 :(得分:8)

您有一项业务要求,即这两个属性的组合是唯一的。因此,您应该对这两个属性有UNIQUE约束。无论您将UNIQUE约束称为“主要”,实际上只是一种偏好,除文档外,它没有太大影响。

唯一的问题是,您是否添加了一个额外的列并将其标记为UNIQUE。我能看到这样做的唯一原因是性能,这是一个合理的原因。

就个人而言,我不喜欢将每个数据库转换为基本上是图形的方法,其中生成的列基本上是指针,而您只是从一个遍历到另一个。我认为这会抛弃关系系统的所有优点。如果您退一步考虑一下,那么您将引入一堆对您的业务毫无意义的列。您可能对我的related blog post感兴趣。

答案 3 :(得分:6)

我相信复合键创建了一个非常自然和描述性的数据模型。我的经验来自Oracle,我不认为在创建复合PK时存在任何技术问题。事实上,任何分析数据字典的人都会立即了解有关该表的内容。在您的情况下,很明显每个source_id必须具有唯一的id_on_source。

自然键的使用经常会引起热烈的争论,但是我使用的人就像从良好的数据模型角度来看自然键。

答案 4 :(得分:3)

我使用复合主键的唯一时间是当键的高位部分是另一个表的键时。例如,我可能会创建一个OrderLineItem表,其主键为OrderId + LineNumber。对OrderLineItem表的许多访问将是“使用(orderid)订购连接orderlineitem”或其中的一些变体,这通常很方便。在查看数据库转储时,还可以轻松找出哪些订单项与哪个订单相关联。

正如其他人所指出的那样,复合键在大多数其他情况下都很痛苦,因为你的联接必须涉及所有部分。更多的是输入意味着更多的错误,查询更慢等等。

两部分钥匙也不错;我经常这样做。我不愿意使用三部分键。超过三部分,我会说忘掉它。

在您的示例中,我怀疑使用复合键几乎无法获得。只需创建一个新的序列号,并让源和源键成为普通属性。

答案 5 :(得分:2)

我遇到了使用大量复合键的问题,所以我不推荐它(更多下面),我也发现在尝试滚动时,在独立/代理键(而不是自然)中有好处回到用户的错误。 问题是通过一组关系,一个表连接了两个表,其中复合的每一行部分是相同的(这在第三范式中是合适的 - 父母的两个部分之间的比较)。我在连接表中重复删除了复合关系的那部分(所以代替parent1ID,other1ID,parent2ID,other2ID有parentID,other1ID,other2ID)但是现在关系无法更新对主键的更改,因为它尝试了通过每条路线做两次而在中间失败。

答案 6 :(得分:1)

有人建议您使用全球唯一ID(GUID):merge replication and transactional replication with updating subscriptions use uniqueidentifier columns to guarantee that rows are uniquely identified across multiple copies of the table。如果值在创建时全局唯一,那么您不需要添加source_id以使其唯一。


虽然uniqueid是一个很好的主键,但我同意使用不同的,自然的(不一定是唯一的)键作为聚簇索引通常会更好。例如,如果uniqueid是标识员工的PK,您可能希望将聚簇索引作为部门(如果您的select语句通常检索给定部门中的所有员工)。如果您确实想使用unqiqueid作为聚簇索引,请参阅NEWSEQUENTIALID()函数:这将创建顺序uniqueid值,这些值(顺序)具有更好的聚类性能。

答案 7 :(得分:1)

添加额外的ID列将使您不得不强制执行两个唯一性约束。

使用额外的ID列作为其他引用表中的外键,而不是自然呈现的键,将导致您必须执行更多连接,即在所有需要原始soruce_ID和ID_on_source的情况下使用来自引用表的数据。