7

在代理键和自然键之间有一个健康的争论:

所以发布 1

所以发布 2

我的观点似乎与大多数人一致(这是微弱的多数),您应该使用代理键,除非自然键是完全明显的并且保证不会改变。然后你应该对自然键强制唯一性。这意味着几乎所有时间都使用代理键。

两种方法的示例,从 Company 表开始:

1:代理键:表有一个ID字段,即PK(和一个身份)。公司名称在各州必须是唯一的,因此那里有一个唯一的约束。

2:自然键:表使用CompanyName和State作为PK——同时满足PK和唯一性。

假设公司 PK 在其他 10 个表中使用。我的假设是,没有数字支持,代理键方法在这里会快得多。

对于自然键,我看到的唯一令人信服的论点是使用两个外键作为自然键的多对多表。我认为在这种情况下这是有道理的。但是如果你需要重构,你可能会遇到麻烦;我认为这超出了这篇文章的范围。

有没有人看过一篇文章比较使用代理键的一组表与使用自然键的同一组表的性能差异?环顾 SO 和谷歌并没有产生任何有价值的东西,只是大量的理论。


重要更新:我已经开始构建一组测试表来回答这个问题。它看起来像这样:

  • PartNatural - 使用唯一 PartNumber 作为 PK 的零件表
  • PartSurrogate - 使用 ID (int, identity) 作为 PK 并在 PartNumber 上具有唯一索引的部件表
  • 植物 - ID (int, identity) 作为 PK
  • Engineer - ID (int, identity) 作为 PK

每个零件都连接到工厂,工厂中零件的每个实例都连接到工程师。如果有人对此测试台有疑问,现在是时候了。

4

2 回答 2

9

两者都用!自然键可防止数据库损坏(不一致可能是一个更好的词)。当“正确的”自然键(以消除重复行)由于长度或所涉及的列数而性能不佳时,出于性能目的,也可以添加代理键以用作其他表中的外键,而不是自然键...但是自然键应保留为备用键或唯一索引,以防止数据损坏并强制数据库一致性...

大部分的hoohah(在这个问题的“辩论”中)可能是由于一个错误的假设——你必须在其他表中使用主键来连接和外键。这是错误的。您可以使用 ANY作为其他表中外键的目标。它可以是主键、备用键或任何唯一索引或唯一约束。只要它在目标关系(表)中是唯一的。至于连接,您可以使用任何东西作为连接条件,它甚至不必是键、索引,甚至是唯一的!(尽管如果它不是唯一的,您将在它创建的笛卡尔积中获得多行)。您甚至可以使用非特定条件(如 >、< 或“like”作为连接条件)创建连接。

实际上,您可以使用任何计算为布尔值的有效 SQL 表达式来创建连接。

于 2009-08-04T19:18:46.297 回答
3

自然键与代理键的值不同,而不是类型。

任何类型都可以用于代理键,例如VARCHAR系统生成的slug或其他的。

但是,代理键最常用的类型是INTEGERand (或您为's使用的RAW(16)任何类型),RDBMSGUID

比较代理整数和自然整数(如SSN)需要完全相同的时间。

比较VARCHARs make 考虑了排序规则,它们通常比整数长,这使得它们的效率降低。

比较一组两个INTEGER可能也比比较单个的效率低INTEGER

在较小的数据类型上,这种差异可能是获取页面、遍历索引、获取数据库锁等所需时间的百分比。

这里是数字(in MySQL):

CREATE TABLE aint (id INT NOT NULL PRIMARY KEY, value VARCHAR(100));
CREATE TABLE adouble (id1 INT NOT NULL, id2 INT NOT NULL, value VARCHAR(100), PRIMARY KEY (id1, id2));
CREATE TABLE bint (id INT NOT NULL PRIMARY KEY, aid INT NOT NULL);
CREATE TABLE bdouble (id INT NOT NULL PRIMARY KEY, aid1 INT NOT NULL, aid2 INT NOT NULL);

INSERT
INTO    aint
SELECT  id, RPAD('', FLOOR(RAND(20090804) * 100), '*')
FROM    t_source;

INSERT
INTO    bint
SELECT  id, id
FROM    aint;

INSERT
INTO    adouble
SELECT  id, id, value
FROM    aint;

INSERT
INTO    bdouble
SELECT  id, id, id
FROM    aint;

SELECT  SUM(LENGTH(value))
FROM    bint b
JOIN    aint a
ON      a.id = b.aid;

SELECT  SUM(LENGTH(value))
FROM    bdouble b
JOIN    adouble a
ON      (a.id1, a.id2) = (b.aid1, b.aid2);

t_source只是一个带有1,000,000行的虚拟表。

aintadouble,bintbdouble包含完全相同的数据,除了它aint有一个整数作为 a PRIMARY KEY,而adouble有一对两个相同的整数。

在我的机器上,两个查询都运行了 14.5 秒,+/- 0.1 秒

性能差异(如有)在波动范围内。

于 2009-08-04T18:44:18.380 回答