每个表真的需要一个自动递增的人工主键吗?

Jas*_*ett 13 sql database relational-database relational-model

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

这对我来说显而易见,但是再一次,似乎没有其他人像我一样到达并采取相同的逻辑结论,所以我必须假设我错了.

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

LBu*_*kin 20

这个问题在SO上被多次提出过,并且多年来(以及之间)开发人员和DBA之间一直是争论的主题.

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

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

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

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

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

通过为每一行提供一个简短,稳定,唯一的值,我们可以减小数据库的大小,提高其性能,并减少存储外键的从属表的波动性.关键多态性也有好处,我稍后会介绍.

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

我们还来看一下您在问题中描述的示例.

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

从表面上看,缩写字段看起来符合良好主键的要求.它相对较短,很容易作为外键传播,看起来很稳定.不幸的是,你没有控制缩写的集合......邮政服务.这是一个有趣的事实:1973年,USPS将内布拉斯加州的缩写从NB改为NE,以尽量减少与加拿大新不伦瑞克省的混淆.故事的寓意是自然键通常不在数据库的控制范围之内......它们可以随着时间的推移而改变.即使你认为他们做不到.对于像人或产品等更复杂的数据,这个问题更加明显.随着业务的发展,使这些实体成为唯一的定义可能会发生变化.这可能会给数据建模人员和应用程序开发人员带来严重问题.

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

别误会我的意思.我并不是在提倡总是使用代理键.在现实世界中,从来没有,永远都是采取危险的立场.代理键的最大缺点之一是它们可能导致表具有由许多"无意义"数字组成的外键.这可能会使解释记录含义变得很麻烦,因为您必须加入或查找其他表中的记录才能获得完整的图片.它还可以使分布式数据库部署更加复杂,因为并不总是可以跨服务器分配唯一的递增数字(尽管大多数现代数据库如Oracle和SQLServer通过序列复制来缓解这种情况).


mar*_*c_s 16

没有.

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

在状态缩写和名称的情况下 - 如果有的话,我将使用两个字母的州缩写作为键.

主键必须是:

  • 独特(100%保证!不仅"几乎"独特)
  • 没有

主键应该是:

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

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

因此,虽然您不必拥有自动递增的"人工"(代理)主键,但它通常是一个很好的选择,因为没有自然发生的数据真正取决于成为主键的任务,并且您希望避免使用具有多个列的巨大主键 - 这些只是过于笨重且效率低下.