92

在我团队的一次相当热烈的讨论中,我被迫思考大多数人喜欢什么作为主键。我们有以下小组-

  1. Int/ BigInt 哪个自动增量是足够好的主键。
  2. 应该至少有 3 列构成主键。
  3. Id、GUID 和人类可读的行标识符都应该区别对待。

PK的最佳方法是什么?如果你能证明你的观点是正确的,那就太棒了。有没有比上述更好的方法?

编辑:任何人都有一个简单的示例/算法来为可扩展的行生成人类可读的标识符?

4

26 回答 26

86

如果您打算在偶尔连接的应用程序的数据库之间进行任何同步,那么您应该使用 GUID 作为主键。调试有点痛苦,所以除了这种情况,我倾向于坚持使用自动增量的整数。

自动增量整数应该是您的默认值,使用它们应该是合理的。

于 2008-12-31T21:21:47.590 回答
58

我没有看到一个答案指出(我认为)真正的基本点 - 即,主键是保证你不会在同一个现实世界实体的表中获得两个条目(如在数据库中建模)。这一观察有助于确定主键的哪些是好的选择,哪些是不好的选择。

例如,在(美国)州名称和代码表中,名称或代码可以是主键 - 它们构成两个不同的候选键,并且选择其中一个(通常较短 - 代码)作为主键首要的关键。在函数依赖理论(以及连接依赖——1NF 到 5NF)中,候选键比主键更重要。

举个反例,人名通常是主键的错误选择。有很多人叫“约翰·史密斯”或其他类似的名字;即使考虑到中间名(记住:不是每个人都有一个 - 例如,我没有),重复的空间很大。因此,人们不使用名称作为主键。他们发明了人工密钥,例如社会安全号码 (SSN) 或员工号码,并使用它们来指定个人。

理想的主键是简短的、独特的、令人难忘的和自然的。在这些特征中,唯一性是强制性的;鉴于现实世界数据的限制,其余部分必须灵活调整。

因此,在确定给定表的主键时,您必须查看该表代表什么。表中的哪组或哪组列值唯一标识表中的每一行?这些是候选键。现在,如果每个候选键由 4 或 5 列组成,那么您可能会认为它们太笨拙而不能成为一个好的主键(主要是因为简短)。在这种情况下,您可能会引入一个代理键 - 一个人工生成的数字。很多时候(但不总是)一个简单的 32 位整数对于代理键就足够了。然后将此代理键指定为主键。

但是,您仍然必须确保其他候选键(因为代理键也是候选键,以及选择的主键)都作为唯一标识符维护 - 通常通过对这些列集放置唯一约束。

有时,人们发现很难确定是什么让一行独一无二,但应该有办法做到这一点,因为简单地重复一条信息并不能使它变得更加真实。如果您不小心并且确实获得了两行(或更多)声称存储相同信息的行,然后您需要更新信息,则存在仅更新一行的危险(尤其是如果您使用游标)而不是每一行,所以行不同步,没有人知道哪一行包含正确的信息。

在某些方面,这是一个相当强硬的观点。

在需要时使用 GUID 并没有什么特别的问题,但它们往往很大(如 16-64 字节),而且使用得太频繁了。通常一个非常好的 4 字节值就足够了。使用 4 字节值就足够的 GUID 会浪费磁盘空间,并且会减慢对数据的索引访问,因为每个索引页的值较少,因此索引会更深,必须读取更多页才能到达信息。

于 2009-01-01T03:09:08.590 回答
27

这只是一个宗教问题,因为人们寻求一个普遍的正确答案。您的团队和这个 SO 线程都显示出如此多的分歧这一事实应该是一个线索,表明有充分的理由在不同的情况下使用您描述的所有解决方案。

  • 当表中没有其他属性或属性集适合唯一标识行时,代理键很有用。
  • 如果可能,自然键是首选,以使表格更易于阅读。自然键还允许依赖表中的外键包含实际值而不是代理 id。例如,当您需要存储state(CA、TX、NY)时,您不妨使用char(2)自然键而不是 int。
  • 在适当的地方使用复合主键。当存在完美的复合键时,不要不必要地添加“ id”代理键(在多对多表中尤其如此)。每个表中的三列键的授权绝对是胡说八道。
  • 当您需要在多个站点上保持唯一性时,GUID 是一种解决方案。如果您需要主键中的值是唯一的,但不是有序的或连续的,它们也很方便。
  • INT 与 BIGINT:表需要64 位范围的主键并不常见,但随着 64 位硬件可用性的增加,它不应该成为负担,并且可以确保不会溢出。INT 当然更小,所以如果空间非常宝贵,它可以带来一点优势。
于 2008-12-31T22:01:42.843 回答
20

我喜欢将数据库程序员博客作为此类信息的来源。

主键的 3 列?我会说,列应该根据业务规则的要求具有适当的唯一约束,但我仍然有一个单独的代理键。复合键意味着业务逻辑进入键。如果逻辑发生变化,您的整个架构就会被搞砸。

于 2008-12-31T21:23:02.050 回答
15

我喜欢我独一无二的。

于 2009-01-06T12:14:40.643 回答
11

有点离题,但我觉得有必要插话...

如果您的主键是 GUID,请不要将其设为聚集索引。由于 GUID 是非顺序的,因此几乎每次插入期间,数据都会在磁盘上重新排列。(糟糕。)如果使用 GUID 作为主键,它们应该是非聚集索引。

于 2009-01-03T03:23:53.573 回答
10

我总是使用代理键。代理键(通常是标识列、自动增量或 GUID)是其中键不存在于数据本身中的键。另一方面,自然键是其自身唯一标识行的键。据我所知,生活中几乎没有真正的天然钥匙。甚至美国的 SSN 之类的东西也不是自然密钥。复合主键是一场等待发生的灾难。您无法编辑任何数据(这是任何自然键的主要缺点,无论是否组合),但更糟糕的是,使用组合键,现在您必须将该键数据永久保存到每个相关表中。多么巨大的浪费。

现在,为了选择代理键,我坚持使用标识列(我主要在 MS SQL Server 中工作)。GUID 太大,Microsoft 建议不要将它们用作 PK。如果您有多个服务器,您需要做的就是增加 10 或 20 或任何您认为需要同步/扩展的服务器的最大数量,并为每个后续服务器上的每个表添加种子,并且您将永远不会发生数据冲突。

当然,由于增量,我将标识列设为 BigInt(也称为长 [64 位])。

做一些数学运算,即使增量为 100,表中仍然可以有 92,233,720,368,547,758(> 92 万亿)行。

于 2008-12-31T22:15:52.677 回答
9

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

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

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

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

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

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

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

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

至于 GUID,使用它们时要非常小心,因为在索引中使用 guid 可以处理索引碎片。用于创建它们的最常用算法将 guid 的“随机”部分放在最重要的位位置......这增加了在添加新行时对常规索引碎片整理/重新索引的要求。

于 2008-12-31T22:02:17.493 回答
8

您永远不应该做的一件事是使用智能钥匙。这是一把钥匙,其中有关记录的信息被编码在钥匙本身中,它最终会咬你。

我在一个地方工作,主键是帐户 ID,它是字母和数字的组合。我不记得任何细节,但是,例如,那些属于某种类型的帐户将在 600 范围内,而另一种类型的帐户则以 400 开头。那太好了,直到那个客户决定同时要求两者工作类型。或者改变他们所做的工作类型。

另一个地方,使用树中的位置作为记录的主键。所以会有如下的记录。

Cat1.subcatA.record1
Cat1.subcatA.record2
Cat1.subcatB.record1
Cat2.subcatA.record1

当然,客户首先想要的是一种在树上移动物品的方法。整套软件在那之前就死了。

拜托,拜托,拜托,如果你正在编写我必须维护的代码,请不要使用智能钥匙!

于 2008-12-31T22:25:09.600 回答
4

我喜欢将自动增量作为主键。我内心深处知道这是一种逃避,但它确实可以很容易地按添加数据的时间对数据进行排序(ORDER BY ID DESC,f'r instance)。

3 列对于人类解析来说听起来非常刺耳。

这就是权衡——你需要多少关系能力,而不是让这个表在此处被人类询问(相对于存储过程或编程接口)是可以理解的。

自动增量适用于我们人类。:-(

于 2008-12-31T21:20:13.130 回答
4

一般来说,这取决于。

就个人而言,我喜欢自动增量整数。

但是,我可以告诉你的一件事是永远不要相信来自其他来源的数据作为你的密钥。我发誓,每次我这样做时,它都会回来咬我。好吧,再也不会了!

于 2008-12-31T21:21:01.397 回答
3

应该至少有 3 列构成主键。

我不明白这一点。

您是在谈论“自然键”,例如“姓名和出生日期”吗?如果存在自然键可能是理想的,但自然键的大多数候选者不是唯一的(几个同名的人)或不是恒定的(有人可以更改他们的名字)。

Int/ BigInt 哪个自动增量是足够好的主键。

我更喜欢吉德。自动增量的一个潜在问题是值(例如“订单 ID”)由数据库实例(例如“销售数据库”)分配......如果您需要合并由多个数据库实例创建的数据(例如,来自多个销售办事处的数据,每个销售办事处都有自己的数据库)。

于 2008-12-31T21:25:07.270 回答
3

RE GUID

注意这是否会成为一个非常非常非常非常的数据库、大量负载和快速访问。

在我的上一份工作中,我们拥有 100 到 5 亿条记录的数据库,我们的数据库人员强烈反对 GUID,并要求使用适当大小的十进制数。他们认为(在 Oracle 下)字符串 Guid 与十进制值的内部存储大小差异会在查找中产生非常明显的差异。(更大的键=要遍历的更深的树)

GUID 的随机性也显着降低了索引页的填充因子——这极大地增加了撕裂和磁盘 I/O。

于 2009-01-01T01:03:38.063 回答
2

这是一个经典的“它取决于”。每个项目都没有一个正确的答案。对于不同的情况,我喜欢不同的东西。这取决于我是否使用 ORM 以及它支持什么。它取决于整体架构(分布式与否等)。只需选择一个您认为可行的选项,然后继续争论制表符和空格即可。

于 2008-12-31T21:16:34.777 回答
2

我倾向于使用选项 #1 或 #3,具体取决于大小、连接的人数以及是否是多数据库服务器的情况。

选项#2对我来说没有多大意义。如果这三个中的任何一个不足以识别唯一记录,那么有可能(无需经过额外的阴谋)两个记录在所有三列中显示具有相同值的两个记录。如果您想对这三者的任何组合强制唯一性,那么只需为它们添加一个索引。

于 2008-12-31T21:22:22.253 回答
2

自动增加列。我能够使我的代码与 SQL Server 或 Oracle 无缝协作,一个使用身份,另一个通过我的 DAL 使用序列,我非常高兴。我同意,如果您要进行复制或发送数据以便稍后在处理后接收它,则有时需要 GUID。

于 2008-12-31T22:29:17.390 回答
2

我一直使用代理键 - 一个称为“id”的自动递增整数。即使另一个选项很明显,我也可以看到很多这样做的理由:

  • 一致性
  • 数据独立(唯一,不会因格式更改而破坏)
  • 人类可读

...而且没有合理的理由不:

  • 连接中的歧义?- 别名表是一种更好的做法,恕我直言
  • 最佳表?- 每个条目删除一个字节是过早的优化,恕我直言
  • 每桌决定?- 不再一致
  • 缩放问题?- 嗯?为什么?
  • 分层数据结构?- 这是非规范化,完全是另一个宗教主题。我只想说我在理论上在某些情况下是粉丝,但在实践中从来没有:)

我还没有想到或遇到的合理理由总是受到欢迎......

于 2009-01-01T21:18:26.363 回答
1

Guids.时期。

如果您需要扩展或需要通过其他方式分配主键,它们将成为您的朋友。您可以为其他所有内容添加索引。


更新以澄清我的陈述。

我在很多不同类型的网站上工作过。从小型单服务器交易到支持多个数据库和 Web 服务器的大型交易。肯定有一些应用程序可以使用自动递增整数作为主键。但是,这些不符合我做事的模式。

使用 GUID 时,您可以在任何地方生成 ID。它可以由远程服务器、您的 Web 应用程序、数据库本身甚至在多主机情况下的多个数据库中生成。

另一方面,自动递增的 INT 只能在主数据库中安全地生成。同样,如果您的应用程序将与该支持数据库服务器密切相关,并且您不关心横向扩展 ,这可能没问题。

当然,使用 GUID 意味着您必须每晚进行重新索引过程。但是,如果您使用的不是自动递增的 INT,则无论如何都应该这样做。哎呀,即使以 INT 作为主索引,您也可能有其他索引需要重新生成以处理碎片。因此,使用 GUID 并不会完全增加另一个问题,因为无论如何都需要执行这些任务。

如果您查看较大的应用程序,您会注意到一些重要的事情:它们都使用 Base64 编码的 GUID 作为键。原因很简单,GUID 的使用使您能够轻松地进行横向扩展,而在尝试横向扩展 INT 时可能会遇到很多麻烦。

我们最新的应用程序经历了持续大约一个月的大量插入。在那之后,90% 以上的查询都是报告的选择。为了增加容量,我可以在这个大插入期间启动额外的数据库服务器;然后轻松地将它们合并到一个数据库中进行报告。试图用 INT 做到这一点绝对是一场噩梦。

坦率地说,任何时候你集群一个数据库或设置复制,数据库服务器都会要求你在表上有 GUID。因此,如果您认为您的系统可能需要增长,请选择一个好的。

于 2008-12-31T21:27:02.710 回答
1

我只使用自动增量 int 或 GUID。99% 的时间我都使用自动增量 int。当我第一次了解数据库并且从未遇到不使用它们的理由时,这正是我被教导要使用的东西(尽管我知道 GUID 会更好的原因)。

我喜欢自动增量整数,因为它有助于提高可读性。例如,我可以说“看看记录 129383”,并且很容易有人进去找到它。使用几乎不可能做到的 GUID。

于 2008-12-31T21:32:28.457 回答
1

除了一个基本的定义性答案,什么才是好的主键主要留给宗教和休息室争论。如果您有一些东西是并且将始终唯一地映射到单个行,那么它将作为主键正常工作。在此之后,还有其他考虑因素:

  • 主键定义是不是太复杂了?为了遵循“最佳实践”,它是否避免引入不必要的复杂性?
  • 是否有更好的主键可能需要更少的数据库处理开销(即 INTEGER 与 VARCHAR 等)?
  • 我是否绝对确定我的主键的唯一性和定义性不变量不会改变?

最后一个可能是吸引大多数人使用 GUID 或自增整数列之类的东西的原因,因为依赖于地址、电话号码、名字/姓氏等内容,只是不要削减它。我能想到的唯一不变的人是 SSN,但我什至不能 100% 确定那些永远独一无二的人。

希望这有助于增加一些清晰度......

于 2008-12-31T21:45:01.787 回答
1

我处理主键的方式(我觉得是最好的)是避免采用“默认”方式。这意味着,与其只是在一个自动递增的整数上打一个耳光并称它为一天,我会查看问题并说“是否有一个列或一组列将永远是 unqiue 并且不会改变?” 如果答案是肯定的,那么我采取这种方法。

于 2008-12-31T21:49:33.570 回答
1

几乎总是整数。

除了处理更小/更快之外,它们还有其他充分的理由。您更愿意写下哪个 - “404040”或“3463b5a2-a02b-4fd4-aa0f-1d3c0450026c”?

于 2008-12-31T21:57:10.640 回答
1

只是有点相关,但是当我有小型分类表(主要是那些在代码中表示 ENUM 的表)时,我最近开始做的一件事是我将主键设为 char(3) 或 char(4)。然后我让这些主键代表查找值。

例如,我有一个内部销售代理的报价系统。我们有“成本类别”,每个报价行项目都分配有一个......所以我有一个名为“tCostCategories”的类型查找表,其中主键是“MTL”、“SVC”、“TRV”、“TAX”, 'ODC'。查找表中的其他列存储更多详细信息,例如代码的正常英文含义、“材料”、“服务”、“旅行”、“税收”、“其他直接成本”等。

这非常好,因为它不使用比 int 更多的空间,而且当您查看源数据时,您无需链接查找表即可知道值到底是什么。例如,报价行可能如下所示:

1 零件编号 40 美元 MTL
2 其他零件编号 29.99 美元 SVC
3 零件编号2 150 美元 TRV

使用 int 表示类别然后在所有行上链接 1、2、3 会容易得多 - 您的数据就在您面前,而且性能似乎根本没有受到影响(不是我已经真正测试过了。)

就真正的问题而言……我喜欢 RowGUID 唯一标识符。我不是 100% 的,但不是所有行都有内部 RowGuid 吗?如果是这样,那么使用 RowGuid 实际上会比整数(或其他任何东西)占用更少的空间。我所知道的是,如果它足以让 M$ 在 GreatPlains 中使用,那么它对我来说就足够好了。(我应该躲避吗??)

于 2008-12-31T23:56:54.687 回答
1

哦,我使用 GUID 的另一个原因 - 我使用分层数据结构。也就是说,我有一个主键匹配的表“公司”和一个表“供应商”。但我也有一个表“制造商”,它也从公司“继承”。供应商和制造商共有的字段不会出现在这些表中 - 它们出现在公司中。在这种设置中,使用 int 比使用 Guid 更痛苦。至少,您不能使用身份主键。

于 2009-01-01T00:00:00.733 回答
1

我喜欢自然键,只要我可以信任它们。为了使用对主题专家有意义的密钥,我愿意支付少量的性能价格。

对于描述实体的表,应该有一个简单的自然键来标识单个实例,其方式与主题人员所做的相同。如果主题没有实体之一的可靠标识符,那么我将求助于代理键。

对于描述关系的表,我使用复合键,其中每个组件引用一个参与关系的实体,因此是实体表中的一行。同样,使用复合键对性能的影响通常很小。

正如其他人指出的那样,“主键”一词有点误导。在关系数据模型中,使用的术语是“候选键”。一个表可能有多个候选键。从逻辑上讲,每一个都和另一个一样好。选择其中一个作为“主要”并通过该键进行所有引用只是设计师可以做出的选择。

于 2009-01-06T12:03:03.240 回答
0

无论您是否意识到,这都是一个复杂的主题。可能属于此 StackOverflow 常见问题解答部分。

我不应该在这里问什么样的问题?

避免提出主观的、争论的或需要扩展讨论的问题。这是一个可以回答问题的地方!

这已经争论了多年,并将继续争论多年。我看到的唯一共识提示是,答案在某种程度上是可预测的,具体取决于您是否询问 OO 人(GUID 是唯一的方法!),数据建模者(自然键是唯一的方法!),或面向性能的 DBA(INT 是唯一的出路!)。

于 2008-12-31T21:27:06.317 回答