20

我正在设计一个数据库,该数据库将用于存储来自多个不同来源的数据。我存储的实例由原始来源分配了唯一的 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,我不确定它是否对争论很重要,但是假设我更喜欢使用SQLiteand MySQL

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

4

8 回答 8

31

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

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

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

一般来说,我还发现在许多框架和工具产品中对复合主键的支持充其量是“不完整的”,而在其他框架中则不存在

于 2009-09-05T11:24:16.127 回答
14

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

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

这是数据仓库中公认的最佳实践(比您正在做的事情要大得多,但仍然相关),并且有充分的理由。代理提供数据完整性和快速连接。使用自然键可能会很快被烧毁,因此请远离它们作为标识符,并且仅在导入过程中使用它们。

于 2009-09-05T11:29:06.347 回答
8

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

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

就个人而言,我不喜欢将每个数据库本质上变成一个图形的方法,其中生成的列本质上是指针,而您只是从一个遍历到下一个。我认为这抛弃了关系系统的所有伟大之处。如果您退后一步想一想,您将引入一堆对您的业务毫无意义的专栏。您可能对我的相关博文感兴趣。

于 2009-09-05T17:10:25.463 回答
6

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

自然键的使用经常引起激烈的争论,但从良好的数据模型角度来看,与我一起工作的人喜欢自然键。

于 2009-09-05T11:42:49.820 回答
3

几乎我唯一一次使用复合主键是当键的高位部分是另一个表的键时。例如,我可能会使用 OrderId + LineNumber 的主键创建一个 OrderLineItem 表。由于对 OrderLineItem 表的许多访问将是“使用 (orderid) 的 order join orderlineitem”或它的一些变体,这通常很方便。在查看数据库转储时,它还可以很容易地确定哪些行项目与哪些订单相关联。

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

两部分键还不错;我经常这样做。我不愿意使用三部分键。超过三部分,我会说算了。

在您的示例中,我怀疑使用复合键几乎没有什么好处。只需发明一个新的序列号,让源和源键为普通属性。

于 2009-09-05T22:49:15.020 回答
2

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

于 2011-11-16T10:05:19.053 回答
1

有些人建议您使用全局唯一 ID (GUID):合并复制和事务复制与更新订阅使用 uniqueidentifier 列来保证行在表的多个副本中是唯一标识的。如果该值在创建时全局唯一,则无需添加 source_id 以使其唯一。


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

于 2009-09-05T11:35:18.647 回答
1

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

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

于 2009-09-05T16:51:57.090 回答