表中主键的最佳做法是什么?


256

在设计表时,我养成了一种习惯,即拥有一列唯一且由我作为主键的列。这可以通过三种方式来实现,具体取决于需求:

  1. 自动递增的标识整数列。
  2. 唯一标识符(GUID)
  3. 可以用作行标识符列的短字符(x)或整数(或其他相对较小的数字类型)列

数字3将用于较小的查找,大多数情况下是读取的表,这些表可能具有唯一的静态长度字符串代码或数字值,例如年份或其他数字。

在大多数情况下,所有其他表都将具有自动递增的整数或唯一标识符主键。

问题:-)

我最近开始使用没有一致行标识符的数据库,并且主键当前聚集在各个列上。一些例子:

  • 日期时间/字符
  • 日期时间/整数
  • 日期时间/ varchar
  • char / nvarchar / nvarchar

有没有有效的理由呢?对于这些情况,我将始终定义一个标识或唯一标识符列。

此外,还有许多根本没有主键的表。这样做的有效原因是什么?

我试图理解为什么表是按原样设计的,这对我来说似乎是一团糟,但也许有充分的理由。

第三个问题可以帮助我解释答案:如果使用多列组成复合主键,与代理/人工键相比,此方法是否具有特定优势?我主要是在性能,维护,管理等方面进行思考?


我发现《数据库技能:选择主键的明智方法》是一本不错的书,并且我遵循了概述的大多数要点。
user2864740

Answers:


254

我遵循一些规则:

  1. 主键应尽可能小。首选数字类型,因为数字类型以比字符格式更紧凑的格式存储。这是因为大多数主键都将是另一个表中的外键以及在多个索引中使用。键越小,索引越小,将使用的缓存中的页面越少。
  2. 主键永远不会改变。更新主键始终是不可能的。这是因为它最有可能在多个索引中使用并用作外键。更新单个主键可能会引起更改的连锁反应。
  3. 不要将“您的问题主键”用作逻辑模型主键。例如,护照号,社会保险号或雇员合同号,因为这些“主键”可以根据实际情况而更改。

关于代理键与自然键,我参考上面的规则。如果自然键很小并且永远不会更改,则可以将其用作主键。如果自然键很大或可能会更改,则使用代理键。如果没有主键,我仍然会使用代理键,因为经验表明您将始终向表中添加表,并希望您将主键放在适当的位置。


3
我喜欢!您是否有任何文件可作为“规则”的依据?谢谢!
劳埃德·科滕

4
不,只是经验。当处理“小型”数据库时,这些东西并不重要。但是,当您处理大型数据库时,所有的小事都很重要。试想一下,与使用文本或guid的行相比,如果您有10亿行具有int或long pk的行。有很大的不同!
Logicalmind

44
只需记住,当您使用人工密钥时,将唯一索引放在自然密钥上(如果确实存在这种情况通常不是这种情况)。
HLGEM

3
@Lloyd Cotten:这是大数据引擎提供商为了支持规则1:skyfoundry.com/forum/topic/24而说的话。它说服了我回到Ints
滚刀2013年

4
即使您“知道”“自然键很小并且永远不会改变”,也要三思而后行。“我们永不重复使用这些代码”是著名的遗言....关于iso以及其他标准(国家代码,iata机场代码),属于小型,永不更改的类别,唯一不变。诸如“该内部品牌的2个字母代表什么?”之类的事情……在假设“它”永远不会改变之前,请三思,这是远离数据库重建的一项财务决策。
安德鲁·希尔

90

自然经文的人工密钥是数据库社区中的一种宗教辩论-请参阅本文及其链接的其他文章。我既不赞成始终使用人工密钥,也不赞成永远使用它们。我将视具体情况决定,例如:

  • 美国:我要使用state_code(德克萨斯州等为“ TX”),而不是德克萨斯州使用state_id = 1
  • 员工:我通常会创建一个人工的employee_id,因为很难找到其他可行的方法。SSN或同等学历可能会起作用,但是可能会出现一些问题,例如新入职者尚未提供其SSN。
  • 员工薪资历史:(employee_id,开始日期)。我不会创建人工的employee_salary_history_id。它会起到什么作用(“愚蠢的一致性”除外)

无论在何处使用人工密钥,都应始终在自然密钥上声明唯一约束。例如,如有必要,请使用state_id,但最好对state_code声明一个唯一约束,否则,您一定会最终得到以下结果:

state_id    state_code   state_name
137         TX           Texas
...         ...          ...
249         TX           Texas

9
在某些情况下,使用SQL Server 2005/2008,自然(文本)键可能比int键快。我有一个带有7-8个字符友好代码的应用程序,我们将其用作主键,并且比int代理更快(并且通常更方便)。无论如何,我们都需要代码,以便我们可以拥有人类可读/易记的代码,我们可以安全地将其安全地传输到另一个应用程序实例(多个站点聚合成一个更大的站点)。
兰巴克2011年

1
+1好答案。但是,我希望人事人员成为雇员标识符的可信赖来源,即负责验证现实生活中可能使用诸如SSN,接受参考等标识符的雇员的人事。人事部门必须是受信任的员工标识符的来源,而不是DBMS!
2012年

@ onedaywhen-我不会。相信人事干事。人们离开,新来的人们有不同的想法。向他们提供他们认为唯一/想要使用的标识符的访问权限,但在数据库内部,dba应自行做出决定
Dave Pile

1
请注意,SSN不一定在每个国家/地区都唯一。至少在奥地利,多个人可能共享相同的号码
maja

同样在某些国家/地区(我认为甚至在美国),他们实际上建议不要共享SSN。
Stijn de Witt

25

只是对经常被忽略的东西的额外评论。有时,不使用代理键在子表中会有好处。假设我们有一个设计,可以让您在一个数据库中运行多个公司(也许是托管解决方案,等等)。

假设我们有以下表和列:

Company:
  CompanyId   (primary key)

CostCenter:
  CompanyId   (primary key, foreign key to Company)
  CostCentre  (primary key)

CostElement
  CompanyId   (primary key, foreign key to Company)
  CostElement (primary key)

Invoice:
  InvoiceId    (primary key)
  CompanyId    (primary key, in foreign key to CostCentre, in foreign key to CostElement)
  CostCentre   (in foreign key to CostCentre)
  CostElement  (in foreign key to CostElement)

如果最后一位没有意义,它Invoice.CompanyId是两个外键的一部分,一个到CostCentre表,另一个到CostElement表。主键是(InvoiceIdCompanyId)。

在这个模型中,这是不可能的螺丝,并引用CostElement从一个公司和一个CostCentre从另一家公司。如果在CostElementCostCentre表上使用了替代键,则将使用。

搞砸的机会越少越好。


6
当使用代理键时,这是一个未被充分引用的缺点。如果表具有代理键,我仍然可以将其用于这些类型的约束。不幸的是,尽管约束条件需要一个索引,并且当(surrogate_key)自身唯一时,在(surrogate_key,other_column)上创建唯一索引只是很奇怪。同样,(other_column)在映射表中通常是完全多余的,因为(surrogate_key)在外来表中是唯一的。代理人真的可以把事情搞糟。
塞缪尔·丹尼尔森

24

我避免使用自然键的原因很简单-人为错误。尽管通常可以使用自然唯一标识符(SSN,VIN,帐号等),但它们需要人工才能正确输入。如果您使用SSN作为主键,那么有人在输入数据时会转置几个数字,而不会立即发现错误,那么您将面临更改主键的麻烦。

我的主键全部由数据库程序在后台处理,用户从未意识到它们。


1
我已经使用了一些使用SSN或Tax ID作为主键的数据库。在存储和外键引用方面效率低下。更不用说一个人的SSN可以更改。所以我完全同意你的看法。
Alex Jorgenson

13

从各个领域制作主键都没有问题,这就是自然键

您可以使用“身份”列(与候选字段上的唯一索引关联)来创建代理密钥

那是一个古老的讨论。在大多数情况下,我更喜欢代理键。

但是缺少钥匙没有任何借口。

回复:编辑

是的,对此有很多争议:D

除了自然键,我看不出自然键有任何明显的优势。您将始终在Name,SocialNumber或类似名称中思考,而不是idPerson

代理键是自然键所具有的某些问题(例如,正在传播的更改)的答案。

当您习惯代孕时,它看起来更干净,更易于管理。

但是最后,您会发现这只是味觉或思维定式的问题。人们使用自然键“思考得更好”,而其他人则不然。


13
人们用自然键“思考得更好”。机器和数据库不是。
FDCastel

11

表应始终具有主键。如果不是,则应该是一个AutoIncrement字段。

有时人们会省略主键,因为他们传输大量数据,并且可能会减慢(取决于数据库)该过程。但是,应在其后添加。

关于链接表的一些评论,这是正确的,这是一个例外,但是BUT字段应为FK以保持完整性,并且在某些情况下,如果未授权链接中的重复项,则这些字段也可以为主键。由于异常是编程中经常发生的一种简单形式,因此应提供主键以保持数据的完整性。


我同意。在要插入大量数据的情况下,请删除主键约束(或在TSQL中使用INSERT IDENTITY ON),然后再放回去:)
Andrew Rollings

1
也有例外:链接表很明显
annakata

另一个原因:如果没有PK /唯一键,则表浏览器(我的意思是类似Access / SQL Server Management Studio之类的表)将拒绝更新/删除具有重复行的单行。您必须为此编写SQL。
丹尼斯C”

从数据仓库事实表中省略PK是很常见的。在Oracle中,您可以在短期内将ROWID伪列引用为唯一标识符(即,不要将其存储在某个地方,并希望它不会发生变化)
David Aldridge,

9

除了所有这些好的答案,我只想分享我刚刚读的一篇很好的文章,伟大的主键辩论

仅列举几点:

为每个表选择主键时,开发人员必须应用一些规则:

  • 主键必须唯一地标识每个记录。
  • 记录的主键值不能为null。
  • 创建记录时,主键值必须存在。
  • 主键必须保持稳定-您不能更改主键字段。
  • 主键必须紧凑并且包含最少的属性。
  • 主键值无法更改。

自然键(倾向于)违反了规则。代理键符合规则。(您最好通读该文章,这值得您度过!)


7

主键有什么特别之处?

表在模式中的用途是什么?表键的目的是什么?主键有什么特别之处?关于主键的讨论似乎遗漏了一点,即主键是表的一部分,而该表是模式的一部分。对于表和表关系最好的方法是驱动使用的键。

表(和表关系)包含有关您希望记录的信息的事实。这些事实应该是自包含的,有意义的,易于理解的并且是不矛盾的。从设计的角度来看,从架构中添加或删除的其他表不应影响所讨论的表。必须有一个目的是存储仅与信息本身有关的数据。了解表中存储的内容不需要进行科学研究项目。为相同目的而存储的事实不应存储多次。键是记录的全部或部分信息,它们是唯一的,主键是专门指定的键,它将成为表的主要访问点(即,应选择它以确保数据的一致性和用途,而不仅仅是插入性能)。

  • 旁白:不幸的是,大多数由应用程序程序员设计和开发的数据库的副作用(有时我是)是,对应用程序或应用程序框架最有利的因素通常是表的主要选择。这将导致整数和GUID键(因为它们对于应用程序框架来说很容易使用)和整体表设计(因为它们减少了表示内存中数据所需的应用程序框架对象的数量)。这些应用程序驱动的数据库设计决策在大规模使用时会导致严重的数据一致性问题。以这种方式设计的应用程序框架自然会导致一次表设计。在表中创建“部分记录”,并随时间填充数据。避免了多表交互,或者当应用程序运行不正常时,如果使用多表交互会导致数据不一致。这些设计会导致无意义的数据(或难以理解的数据),分布在表中的数据(您必须查看其他表才能理解当前表)以及重复的数据。

据说主键应该尽可能小。我会说密钥应该只在必要时才大。应避免将无意义的字段随机添加到表中。从随机添加的无意义的字段中创建键甚至更糟,尤其是当它破坏了从另一个表到非主键的联接依赖性时。这仅在表中没有好的候选键的情况下才是合理的,但是如果将其用于所有表,则这种情况肯定表明架构设计不佳。

也有人说,主键永远不应该更改,因为更新主键始终是不可能的。但是更新与删除然后插入的更新相同。按照这种逻辑,您永远不要使用一个键从表中删除一条记录,然后再使用第二个键添加另一条记录。添加代理主键不会删除表中另一个键存在的事实。如果其他表通过代理键(例如具有替代键的状态表的状态描述从“已处理”更改为“已取消”),则更新表的非主键可能会破坏数据的含义。 '肯定会破坏数据)。永远应该避免的是破坏数据含义。

话虽如此,我要感谢当今企业中存在的许多设计不佳的数据库(无意义的代理键数据损坏的1NF庞然大物),因为这意味着对于理解正确的数据库设计的人们来说,这是不计其数的工作。但是令人遗憾的是,有时候它确实会让我感觉像西西弗斯(Sisyphus),但我敢打赌他有一次401k的事故(坠机前)。远离博客和网站,以解决重要的数据库设计问题。如果要设计数据库,请查找CJ Date。您也可以引用Celko for SQL Server,但前提是您必须先站住鼻子。在Oracle方面,请参考Tom Kyte。


1
“按照这种逻辑,您永远不要使用一个键从表中删除一条记录,然后再使用第二个键添加另一条记录。” -有这种情况,这实际上是外键上的“ ON DELETE RESTRICT”子句将执行的操作。在某些情况下(例如,需要审计跟踪的情况),“删除”布尔字段将比允许删除记录更好。
Waz

6

如果有自然密钥,通常最好。因此,如果datetime / char 唯一标识该行,并且两个部分对该行都有意义,那就太好了。

如果只是日期时间有意义,并且只是将char设置为使其唯一,那么您也可以只使用一个identify字段。


9
通常最好?我没有任何科学依据,但我几乎可以肯定,大多数人都喜欢替代钥匙而不是自然钥匙。在许多情况下,没有自然键。
JC。

3
数据库中的任何行都应该始终是自然键。该“自然”密钥可能是在业务环境中或由您的技术系统生成的,但是它应该始终存在。
汤姆H

2
如果在您的世界中,那已被确定为识别表中一行的唯一方法,那么可以。当然,当设计人员选择为PK创建GUID时,通常是因为他们没有完成找到REAL自然键的工作,因此在那种情况下GUID不是自然键。
汤姆H”

8
2.如果您从自然界拿走钥匙,自然界将发生变化以破坏您的钥匙。如果使用电话号码,您将从同一家庭获得两个用户。如果您使用姓氏,他们会结婚。如果您使用SSN,则隐私法律将发生变化,并要求您将其删除。
James Orr

2
@Barry:RE:#2。如果自然世界发生变化并且导致您的自然钥匙发生变化,则意味着您在选择自然钥匙方面做得很差。根据定义,自然键不会随时间变化。
汤姆H”

6

经过25年以上的开发经验,这是我的经验法则。

  • 所有表都应具有一个自动递增的单列主键。
  • 将其包含在任何旨在可更新的视图中
  • 在您的应用程序上下文中,主键不应具有任何意义。这意味着它不应是SKU,帐号,员工ID或对您的应用程序有意义的任何其他信息。它仅仅是与实体关联的唯一密钥。

数据库将主键用于优化目的,除标识特定实体或与特定实体有关之外,应用程序不应将主键用于其他任何用途。

始终只有一个值的主键使执行UPSERT非常简单。

使用其他索引来支持在您的应用程序中有意义的多列键。


5

对我而言,自然键与人工键的关系是您要在数据库中拥有多少业务逻辑。社会安全号码(SSN)是一个很好的例子。

“数据库中的每个客户端都必须并且必须具有SSN。” Bam完成后,使其成为主键并完成操作。只要记住,当您的业务规则发生变化时,您就会被淘汰。

由于我在更改业务规则方面的经验,我自己不喜欢自然键。但是,如果您确定它不会改变,则可能会阻止一些关键的联接。


8
我已经看到了SSN虽然不是唯一的数据。如果要从其他来源导入数据,请非常注意自然键!
HLGEM

2
如果您遭受身份盗用,则可以更改您的社会保险号。ssa.gov网站上列出了四种其他情况,它们将更改您的电话号码。
Zvi Twersky

4

我怀疑原始数据结构的设计师需要Steven A. Lowe的卷起报纸疗法。

顺便说一句,GUID作为主键可能会降低性能。我不推荐。


2
说它的性能消耗是过早的优化。在某些情况下(断开客户端,将来的表合并,复制),需要使用指导
JC。

2
“过早优化”是SO(IMHO)上的一个过度使用的短语!是的,在某些情况下可能需要GUID,但是Andrew指出,无论是否需要,都不应将它们用作默认数据类型。
托尼·安德鲁斯

好的,这实际上不是过早的优化。我的意思是,大多数人没有注意到性能差异所需的音量。是的,如果您知道永远不需要向导,请使用自动增量。
JC。

或同时使用。具有一个基于int / long的主键,用于快速选择和联接,然后具有一个guid字段。至少,这就是我在做什么。错了吗 我不应该那样做吗?:)
Andrew Rollings

我也在使用这两列。但不确定是否错误。您是否发现它@AndrewRollings?
瑜伽师

3

您应使用包含多个字段的“复合”或“复合”主键。

这是一个完全可以接受的解决方案,请转到此处获取更多信息:)


3

我也总是使用数字ID列。在oracle中,我没有任何理由在number(12,0)以上使用number(18,0)(或者是int而不是long),也许我只是不想担心在其中插入数十亿行数据库!

我还包括了一个创建和修改的列(类型为timestamp),用于基本跟踪,在这里看来很有用。

我不介意在其他列组合上设置唯一约束,但是我真的很喜欢我的ID,创建的,修改过的基准要求。


2
我还必须指出,我不会在链接/联接表上放置ID,而仅在包含数据的表上放置ID。
JeeBee

3

我寻找自然的主键,并在可能的地方使用它们。

如果找不到自然键,由于SQL Server使用树,因此我更喜欢GUID而不是INT ++,因为总是在树的末尾添加键是很不好的。

在多对多联接的表上,我使用外键的复合主键。

因为我很幸运能够使用SQL Server,所以我可以使用探查器和查询分析器研究执行计划和统计信息,并了解我的键如何非常轻松地执行。


您是否有任何文档来支持该语句:“如果找不到自然键,则我更喜欢使用GUID而不是INT ++,因为SQL Server使用树,并且总是在树的末尾添加键是很不好的。” 不用怀疑,只是尝试编译一些文档。
劳埃德·科腾

1
@Lloyd-很高兴您对我发现非常着迷的事物感兴趣。msdn.microsoft.com/zh-cn/library/ms177443(SQL.90).aspx的
Guge

2

我总是使用自动编号或身份字段。

我为使用SSN作为主键的客户工作,然后由于HIPAA规定被迫更改为“ MemberID”,因此在更新相关表中的外键时会引起很多问题。坚持使用身份列的一致标准帮助我避免了所有项目中的类似问题。


6
开发人员对自然键的选择不佳并不意味着自然键不好。
汤姆H”

1
难于使用的工具在某种程度上不是针对该工具的吗?
Sqeaky

1

所有表具有主键。否则,您拥有的就是HEAP-在某些情况下,这可能就是您想要的(例如,当数据随后通过服务代理复制到另一个数据库或表时,将产生大量的插入负载)。

对于行数少的查找表,可以使用3 CHAR代码作为主键,因为它比INT占用的空间少,但是性能差异可以忽略不计。除此之外,我将始终使用INT,除非您有一个引用表,该引用表可能具有由关联表中的外键组成的复合主键。


1

如果您真的想通读这个古老的辩论中的所有来回内容,请在Stack Overflow上搜索“自然键”。您应该返回结果页面。


1

图形用户界面可以用作主键,但是您需要创建正确的GUID类型,以使其性能良好。

您需要生成COMB GUID。关于它和性能统计的一篇不错的文章是 GUID作为主键的成本

另外,有关在SQL中构建COMB GUID的一些代码也位于Uniqueidentifier与身份存档)中


5
恕我直言,只有在需要跨数据库同步数据时才应使用guid。自动生成的ID在其中是有问题的。使用guid和使用基本数字类型之间的区别在于,guid每行需要16个字节,而数字则小得多。
Logicalmind

如果您转到我上面提供的链接,则使用COMB Guid的性能几乎没有差别。
Donny V.

0

我们进行了大量的联接,并且复合主键刚刚成为性能猪。即使您要引入第二个候选键,简单的int或long变量仍可以解决许多问题,但是在一个字段而不是三个字段上加入要容易得多,也更容易理解。


1
当您现在必须遍历6个表以连接所需的实际两个表时,这种策略就会分崩离析,因为没有传播复合键。最终还需要为多个插入使用循环/游标,这可能是巨大的性能消耗。
汤姆H”

2
我并不重要要学习新知识。我希望看到您所说的例子,将一些理性的事实注入其中一些宗教论点会有所帮助。
丹·布莱尔

0

我会优先考虑我对自然键的偏爱-尽可能使用它们,因为它们会使您的数据库管理工作变得更加轻松。我在公司中建立了一个标准,所有表都包含以下列:

  • 行ID(GUID)
  • 创建者(字符串;具有当前用户名的默认值(SUSER_SNAME()在T-SQL中))
  • 已创建(DateTime)
  • 时间戳记

行ID在每个表上都有一个唯一键,并且在任何情况下都是每行自动生成的(并且权限会阻止任何人对其进行编辑),并且可以合理地保证在所有表和数据库中唯一。如果任何ORM系统需要单个ID密钥,则使用该ID密钥。

同时,如果可能的话,实际PK是自然键。我的内部规则是这样的:

  • 人员-使用代理密钥,例如INT。如果位于内部,则Active Directory用户GUID是可接受的选择
  • 查找表(例如StatusCodes)-使用简短的CHAR代码;与INT相比,它更容易记住,并且在许多情况下,纸质表格和用户也会为了简洁而使用它(例如,状态=“ E”表示“过期”,“ A”表示“已批准”,“ NADIS”表示“未检测到石棉”样品中”)
  • 链接表-FK的组合(例如EventId, AttendeeId

因此,理想情况下,您将获得一个自然,易于理解且令人难忘的PK,以及一个ORM友好的每表一个ID的GUID。

注意:我维护的数据库倾向于记录10万个记录,而不是数百万个或数十亿个,因此,如果您有使用较大系统的经验,这些经验与我的建议相矛盾,请随时忽略我!


1
您是否建议为没有强自然键的表同时创建SK GUID INT SK?

您不必这样做,但是好处是:a)如果需要复制,它使复制更加容易,b)在处理ORM时,可以在代码中为对象分配唯一的ID,然后再保存它(这对您很有用)必须先对您的对象进行大量编辑,然后再保存到会话缓存中)。关键是该实例中的INT。GUID只是一种奖励。
基思·威廉姆斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.