13

在我 7 年的开发经验中,我见过的每个数据库中的几乎每个表都有一个自动递增的主键。为什么是这样?如果我有一个美国州表,其中每个州必须有一个唯一的名称,那么自动递增主键有什么用?为什么不直接使用州名作为主键呢?在我看来,这似乎是允许重复伪装成唯一行的借口。

这对我来说似乎很明显,但话又说回来,似乎没有其他人会得出和我一样的逻辑结论,所以我必须假设我很有可能是错的。

我们需要使用自动递增键是否有任何真实、实际的原因?

4

9 回答 9

21

这个问题已经在 SO 上被问过很多次,并且多年来一直是开发人员和 DBA 之间(和之间)争论的主题。

让我首先说你的问题的前提意味着一种方法普遍优于另一种方法......这在现实生活中很少见。代理键和自然键都有其用途和挑战- 了解它们是什么很重要。无论您在系统中做出何种选择,请记住一致性是有好处的——它使数据模型更易于理解,并且更易于开发查询和应用程序。我还想说,对于 PK,我更喜欢代理键而不是自然键……但这并不意味着自然键有时不能在该角色中有用。

重要的是要认识到代理键和自然键不是相互排斥的——在许多情况下它们可以相互补充。请记住,数据库表的“键”只是唯一标识记录(行)的东西。单行完全有可能具有多个键,这些键表示使记录唯一的不同类别的约束。

另一方面,主键是一个特定的唯一键,数据库将使用它来强制引用完整性并在其他表中表示外键。任何表只能有一个主键。主键的基本品质是它是 100% 唯一且非 NULL。主键的理想品质是它是稳定的(不变的)。虽然可变主键是可能的 - 它们会导致数据库出现许多问题,这些问题最好避免(级联更新、RI 故障等)。如果您确实选择为您的表使用代理主键 - 您还应该考虑创建唯一约束以反映任何自然键的存在。

在以下情况下,代理键很有用:

  1. 自然键不稳定(值可能随时间变化)
  2. 自然键很大或笨拙(多列或长值)
  3. 自然键可以随时间变化(随时间添加/删除的列)

通过为每一行提供一个简短、稳定、唯一的值,我们可以减小数据库的大小,提高其性能,并减少存储外键的依赖表的波动性。还有 key polymorphism的好处,我稍后会谈到。

在某些情况下,使用自然键来表达表之间的关系可能会有问题。例如,假设您有一个自然键为 的 PERSON 表{LAST_NAME, FIRST_NAME, SSN}。如果您有一些其他表 GRANT_PROPOSAL,您需要在其中存储对 Proposer、Reviewer、Approver 和 Authorizer 的引用,会发生什么情况。您现在需要 12 列来表达此信息。您还需要提出某种命名约定来识别哪些列属于哪种个体。但是,如果您的 PERSON 表需要 6、8 或 24 列来作为自然键呢?这很快变得难以管理。代理键通过将键的语义(含义)与其作为标识符的用途分开来解决此类问题

让我们看一下您在问题中描述的示例。

是否应该将状态的 2 字符缩写用作该表的主键。

从表面上看,缩写字段似乎满足了良好主键的要求。它比较短,很容易作为外键传播,看起来很稳定。不幸的是,您无法控制缩写集……邮政服务可以。这是一个有趣的事实:1973 年,USPS 将 Nebraska 的缩写从 NB 更改为 NE,以尽量减少与加拿大新不伦瑞克省的混淆。这个故事的寓意是自然键通常不受数据库的控制......并且它们会随着时间而改变。即使你认为他们不能. 对于人或产品等更复杂的数据,这个问题更加明显。随着业务的发展,使这些实体独一无二的定义可能会发生变化。这会给数据建模人员和应用程序开发人员带来重大问题。

前面我提到主键可以支持键多态。这意味着什么?好吧,多态性是一种类型 A 出现并像另一种类型 B 一样使用的能力。在数据库中,这个概念是指将来自不同类别实体的键组合到一个表中的能力。让我们看一个例子。想象一下,您希望在您的系统中有一个审计跟踪,以识别哪些实体在什么日期被哪个用户修改。用以下字段创建一个表会很好:{ENTITY_ID, USER_ID, EDIT_DATE}。不幸的是,使用自然键,不同的实体有不同的键。所以现在我们需要创建一个单独的链接表对于每种类型的实体......并以一种理解不同类型的实体以及它们的键是如何形成的方式构建我们的应用程序。

不要误会我的意思。我并不是主张应该始终使用代理键。在现实世界中,永远、永远、永远是一个危险的位置。代理键的最大缺点之一是它们可能导致表中的外键包含许多“无意义”的数字。这会使解释记录的含义变得很麻烦,因为您必须从其他表中连接或查找记录才能获得完整的图像。它还可以使分布式数据库部署更加复杂,因为在服务器之间分配唯一的递增数字并不总是可能的(尽管像 Oracle 和 SQLServer 这样的大多数现代数据库通过序列复制来缓解这种情况)。

于 2010-10-13T22:03:23.077 回答
17

不。

在大多数情况下,拥有代理INT IDENTITY键​​是一个简单的选择:可以保证它不为 NULL 并且 100% 唯一,这是许多“自然”键不提供的 - 名称可以更改,SSN 和其他项目也可以信息。

对于州缩写和名称 - 如果有的话,我会使用两个字母的州缩写作为键。

主键必须是

  • 独一无二(100% 保证!不仅仅是“几乎”独一无二)
  • 非空

主键应该是:

  • 尽可能稳定(不要改变——或者至少不要太频繁)

状态两个字母的代码肯定会提供这个 - 这可能是自然键的候选者。一个键也应该很小——一个 4 字节的 INT 是完美的,一个两个字母的 CHAR(2) 列也是一样的。我永远不会使用 VARCHAR(100) 字段或类似的东西作为键 - 它太笨重,很可能会一直改变 - 不是一个好的关键候选者。

因此,虽然您不必拥有自动递增的“人工”(代理)主键,但它通常是一个不错的选择,因为没有任何自然发生的数据真正能够胜任作为主键的任务,并且您想要避免拥有包含多列的巨大主键 - 这些太笨重且效率低下。

于 2010-10-13T21:23:28.257 回答
3

我认为在短语“Primary” Key 中使用“Primary”一词在真正意义上具有误导性。

首先,使用“键”是表中必须唯一的一个属性或一组属性的定义,

然后,拥有任何密钥服务于几个经常相互不一致的目的。

目的 1.将与该父表有关系的子表中的一条或多条记录用作连接条件。(在这些子表中显式或隐式定义外键)
目的2。(相关)确保子记录必须在父表中有父记录(子表FK必须作为Key存在于父表中)
目的3。提高需要在表中快速定位特定记录/行的查询的性能。

目的 4。(从数据一致性的角度来看最重要!)通过防止将代表相同逻辑实体的重复行插入到表中来确保数据一致性。(这通常被称为“自然”键,应该由相对不变的表(实体)属性组成。)

显然,任何无意义的非自然键(如 GUID 或自动生成的整数)完全无法满足目的 4。

但通常,对于许多(大多数)表,可以提供 #4 的完全自然的键通常包含多个属性并且过宽,或者太宽以至于将其用于目的 #1、#2 或 #3 会导致不可接受性能后果。

答案很简单。两者都用。对其他子表中的所有联接和 FK 使用简单的自动生成整数键,但要确保每个需要数据一致性的表(很少有表不需要)都有一个备用的自然唯一键,以防止插入不一致的数据行。 .. 另外,如果你总是同时拥有这两者,那么所有反对使用自然键的反对意见(如果它改变了怎么办?我必须改变它被引用为 FK 的每个地方)变得没有实际意义,因为你没有使用它。 ..您只在它是PK的一个表中使用它,以避免不一致的重复数据......

唯一可以在没有两者的情况下逃脱的情况是完全独立的表,它与其他表没有任何关系,并且具有明显且可靠的自然键。

于 2010-10-13T21:35:18.077 回答
2

一般来说,数字主键的性能要好于字符串。您还可以创建唯一键以防止重复出现。这样您就可以确保没有重复,但您也可以获得数字的性能(与您的场景中的字符串相比)。

很可能,主要数据库对基于整数的主键进行了一些性能优化,而基于字符串的主键不存在这些性能优化。但是,这只是一个合理的猜测。

于 2010-10-13T21:22:18.373 回答
1

不,绝对不是。

拥有一个不能更改的主键是一个好主意(UPDATE 对于主键列是合法的,但通常可能会造成混淆,并且会给子行带来问题)。但是,如果您的应用程序有其他一些比自动递增值更合适的候选者,那么您可能应该使用它。

在性能方面,通常更少的列更好,尤其是更少的索引。如果您有另一列具有唯一索引并且永远不能被任何业务流程更改,那么它可能是一个合适的主键。

从 MySQL (Innodb) 的角度来看,使用“真实”列作为主键而不是“人工”列也是一个好主意,因为 InnoDB 总是将主键聚集在一起并将其包含在二级索引中(这就是它找到其中的行)。这使它有可能使用主键进行有用的优化,而其他任何唯一索引都无法做到这一点。MSSQL 用户经常选择聚集主键,但它也可以聚集不同的唯一索引。

编辑:

但是,如果它是一个小型数据库并且您不太关心性能或大小,那么添加一个不必要的自动增量列并不是那么糟糕。

非自动递增值(例如 UUID,或根据您自己的算法生成的其他字符串)可能对分布式、分片或多样化系统有用,因为这些系统很难(或不可能 - 考虑分布式系统继续在网络分区的两侧插入行)。

于 2010-10-13T21:30:08.510 回答
1

我认为有两件事可以解释有时使用自动递增键的原因:

  • 空间考虑;好的,您的州名并不多,但它所占用的空间可能会增加。如果你真的想用它的名字作为主键存储状态,那么继续,但它会占用更多的地方。在某些情况下,这可能不是问题,听起来像是过去的问题,但这种习惯可能已经根深蒂固。我们程序员和 DBA 确实喜欢习惯:D

  • 防御考虑:我最近遇到了以下问题;我们在数据库中有用户,其中电子邮件是所有识别的关键。为什么不将电子邮件设为主键?除了突然出现边界情况,一个人必须在那里两次才能拥有两个不同的地址,并且没有人在规范中谈论它,因此地址没有标准化,并且在这种情况下,两封不同的电子邮件必须指向同一个人并且。 .. 过了一会儿,你停止拉扯你的头发并添加该死的整数 id 列

我不是说这是一个坏习惯,也不是一个好习惯。我确信可以围绕合理的主键设计好的系统,但是这两点让我相信恐惧和习惯是罪魁祸首

于 2010-10-13T21:30:31.520 回答
1

是的,在我看来,每个表都需要一个自动递增的整数键,因为它使 JOIN 和(尤其是)前端编程变得更加容易。其他人感觉不同,但这是 20 多年的经验。

唯一的例外是小的“代码”或“查找”表,我愿意在其中替换一个短的(4 或 5 个字符)TEXT 代码值。我这样做是因为我经常在我的数据库中使用很多这些,它允许我向用户呈现有意义的显示,而无需在查找表中查找描述或将其加入结果集中。您的 States 表示例适合此类别。

于 2010-10-13T21:29:15.110 回答
0

以下是一些实际的考虑。

当有一个整数列作为主键时,大多数现代 ORM(rails、django、hibernate 等)工作得最好。

此外,有一个标准的命名约定(例如 id 作为主键,table_name_id 用于外键)使得识别键更容易。

于 2010-10-13T23:08:04.577 回答
0

它是关系数据库的关键组成部分。使用与状态相关的整数而不是使用整个状态名称可以在数据库中节省大量空间!想象一下,您有一百万条记录引用您的状态表。您想在每条记录上使用 4 个字节作为数字,还是想为每个州名使用一大堆字节?

于 2010-10-13T21:23:42.520 回答