不使用GUID作为主键的原因是什么?

26
每当我设计数据库时,我都会自动为每个表(除了查找表)生成一个自动生成的GUID主键。
我知道我永远不会因为重复的键、合并表格等问题而失眠。在我看来,在所有领域中,任何给定的记录都应该是唯一的,并且这种独特性应该从表格到表格以一致的方式表示。
我意识到这永远不会是最有效的选择,但是把性能放到一边,我想知道是否存在反对这种做法的哲学论据?
根据回答让我澄清:
我所说的是始终使用GUID代理键作为主键-无论是否以及如何在表格上设计任何自然或顺序键。这些是我的假设:
基于自然键的数据完整性可以进行设计,但不能假定。
主键的功能是参照完整性,与性能、排序或数据无关。
8个回答

18

GUID可能看起来是作为主键一个自然的选择 - 如果你真的必须使用它作为表的PRIMARY KEY,那么你可能会争论使用它。

我强烈建议不要将GUID列用作聚集键,这是SQL Server默认情况下的处理方式,除非你明确告诉它不要这样做。这样做的主要原因确实是性能,它将在未来影响你的系统性能(相信我 - 这只是时间问题) - 还有一些资源的浪费(SQL Server机器中的磁盘空间和RAM),这是真的没有必要的。

你需要将两个问题分开看:

1)主键是一个逻辑结构 - 唯一可靠地标识表中每一行数据的候选键之一。这可以是任何东西 - 例如INT、GUID或字符串 - 根据你的应用场景选择最合适的。

2)聚集键(定义表上"聚集索引"的列) - 这是一个物理存储相关的事情,在这里,一个小而稳定的、不断递增的数据类型是你最好的选择 - INT或BIGINT是你默认的选择。

默认情况下,SQL Server表上的主键也用作聚集键 - 但这并不是必须的!当我将先前基于GUID的主/聚集键拆分为两个单独的键时,我个人见过巨大的性能提升 - 主(逻辑)键在GUID上,聚集(排序)键在一个单独的INT IDENTITY(1,1)列上。

正如索引之后的女王Kimberly Tripp和其他人多次指出的那样,将GUID作为聚集键并不是最佳选择,因为由于其随机性,它会导致大量页面和索引碎片以及普遍的性能问题。是的,我知道 - 在SQL Server 2005及以上版本中有newsequentialid(),但即使这样也不是真正的完全顺序,因此也会遇到与GUID相同但稍微不那么突出的问题。还有一个问题需要考虑:表上的聚集键也将添加到表上每个非聚集索引的每个条目中 - 因此您确实希望尽可能使其尽可能小。通常,具有20亿行的INT对于绝大多数表来说应该足够了 - 而与GUID作为聚集键相比,您可以节省数百兆字节的磁盘存储空间和服务器内存。

快速计算 - 使用INT vs. GUID作为主键和聚集键:

  • 基本表格有1'000'000行(3.8 MB vs. 15.26 MB)
  • 6个非聚集索引(22.89 MB vs. 91.55 MB)

总计:25 MB vs. 106 MB - 这仅仅是单个表格!

还有更多值得思考的内容 - Kimberly Tripp的优秀文章 - 读一读,再读一遍,消化一下!这真的是SQL Server索引的福音。

Marc


@THomas:它占用了更多的空间,不仅仅是在磁盘上 - 还包括您服务器的主内存(RAM) - 这是许多人没有考虑到的! RAM并不像磁盘空间那样便宜,更多的空间=更多的I/O=更低的性能(概括和简化)。 - marc_s
@marc_s - 在所有表上添加int聚集键(以及GUID pk)无法解决合并数据库的问题。您仍然需要处理同步标识列的可怕噩梦。此外,您会消耗20个字节的索引空间,而不是只使用16个字节的GUID PK,因为您将需要在GUID列上添加唯一约束。我并不认为比选择代理pk策略(组合guid或int pk)更好地增加一个永远不会使用的int聚集键。 - Thomas
1
@marc_s - 再次说,对于4-8 GB的系统来说,25MB与106MB之间的差别微不足道,这还假设你的表有一百万行。 - Thomas
@James Westgate:当然,即使是索引页也必须加载到内存中,如果在这些页面上浪费空间,就会浪费服务器内存的空间... - marc_s
1
@marc_s - 是的,我认为这是一个很好的观点,谢谢 - 尽管我仍然会把它放在性能而不是哲学的阵营中。 - Yarin
显示剩余6条评论

15

Jeff Atwood在他的博客中详细讨论了这个问题:
http://www.codinghorror.com/blog/2007/03/primary-keys-ids-versus-guids.html

Guid的优点:
在每个表、每个数据库和每个服务器中都是唯一的
允许轻松地合并来自不同数据库的记录
允许轻松地将数据库分布在多个服务器上
可以在任何地方生成 ID,而不必往返到数据库
大多数复制场景都需要 GUID 列

Guid的缺点:
它比传统的 4 字节索引值大 4 倍;如果不小心处理会对性能和存储造成严重影响
调试起来比较麻烦(例如:where userid='{BAE7DF4-DDF-3RG-5TY3E3RF456AS10}')
为获得最佳性能(例如在 SQL 2005 上使用 newsequentialid())并启用聚集索引,生成的 GUID 应该是部分顺序的


2
如果您不进行复制,那么我从未见过使用GUID合并记录的用途,因为通常首要考虑的是内容。此外,我很少因为外键而进行合并,但我希望能够经常这样做。如果我的数据库是分布式的或将被复制,我会添加它们,否则我会依赖日期时间戳。 - databyte
任何超出处理器本地整数大小的内容,在索引操作中都会运行得非常缓慢。此外,还必须考虑到GUID生成通常需要锁定比常规本地序列更新更全局的某些内容。 - Evan Carroll
不错的第一个链接,我会加上http://krow.livejournal.com/497839.html 似乎所有的缺点仍然在性能方面,而这已经变得不那么重要了... - Yarin
@Yarin,性能营地变得不那么重要了吗?你在开玩笑吗?性能对于数据库至关重要。它是数据完整性之后的第二个最重要的事情。 - HLGEM
1
@HLGEM- 我的意思是,由于硬件/软件的改进,GUID与INT的选择对数据库性能的影响越来越不重要了,而不是性能本身变得不重要。 - Yarin
@Yarin 我希望你能理解,我们存储的细节和最终在查询中要求的内容对于我们大多数人来说都需要任何硬件性能提升。 (当我获得更好的硬件时,通常会用于解决积压的功能或已部署功能的性能下降)。 - Christopher McGowan

4

补充说明:

优点

  • 与整数相比,采用此方式几乎不可能让开发人员“意外”向用户公开代理键(这种情况在使用整数时几乎总是会发生)。
  • 将数据库合并变得简单许多倍,比处理标识列容易得多。

缺点

  • 空间占用更大。它的真正问题是它占用每个页面更多的空间和索引中更多的空间,从而使它们变得更慢。在今天的世界中,Guids的额外存储空间实际上无关紧要。
  • 您必须非常小心地创建新值。真正的随机值无法很好地索引。您必须使用COMB guid或添加一个序列元素到guid的某个变体。

"真正独特的值" : INT IDENTITY 可能是您可以获得的最独特的 - 并且它们作为聚集键处理得非常好。问题不在于 GUID 的唯一性,而在于其随机性。 - marc_s
1
@marc_s - 我的措辞不当。我所谓的“超级独特”是指跨越时间和空间。 “随机”可能更为恰当,我会进行调整。 - Thomas
@onedaywhen - 关于创建副本,如果您的意思是创建数据本身的副本,那就是另一回事了。无论您使用什么替代键策略,都必须在表中的其他列上拥有业务键,这与数据库合并无关。 - Thomas
另一个要点是:如果您完全避免使用代理键并始终使用自然键,则无法向用户公开代理键 :) - onedaywhen
“你必须在表中的其他列上拥有业务键,这与数据库合并无关。”——当然,但根据我的经验,“代理键作为主键”的支持者往往不会在候选键、业务键或其他方面上费心,这可能是出于无知或其他原因。 - onedaywhen
显示剩余2条评论

4
你仍然实现了每个表的自然键,不是吗? - 仅使用GUID键显然无法防止重复数据、冗余和随后的数据完整性损失。
假定你确实强制执行其他键,那么在没有例外地向每个表添加GUID可能只会增加不必要的复杂性和开销。这并不能真正使合并不同表中的数据变得更容易,因为你仍然需要修改/去重表的其他键。我建议你应该根据情况评估使用GUID代理。对于每个表都有一个通用规则并不必要或有帮助,因为毕竟每个表都建模了不同的内容。

作为一个数据库设计师,你需要根据具体情况评估和强制数据完整性,并且应该谨慎避免套用通用规则。但在我看来,使用GUID替代键提供了一种公共接口方法来识别记录的唯一性是有价值的。我们可以尝试强制执行数据完整性,但不应该假设它已经被满足,即使自然键规则已经被违反,GUID键至少可以每次提供可靠的记录唯一性。 - Yarin
如果强制执行唯一性约束,则所有键都是“防错”的。我不同意我们不应该假设数据完整性!数据库设计师的第一个责任是创建正确的数据模型 - 确保有关业务的相关事实以避免不准确的结果的方式记录。除非并直到您实现自然键,否则您将无法实现这一点。GUID将无济于事。 - nvogel
@David- 我通常不会假设任何事情,更不用说真实世界的数据完整性了- 预测数据库将如何被软件开发人员和未来的设计师/管理员滥用是不可能的,一旦原始作者领取他的401k。自然键不能百分之百地防范错误,因此具有任意性的代理键更具吸引力,即不依赖于数据- 所以我在谈论保证参照完整性,而不是假设数据完整性。 - Yarin
“无法预测数据库将如何被软件开发人员、未来的设计师和管理员使用/滥用,包括删除 GUID 列。如果数据没有完整性,引用完整性基本上是毫无意义的。创建自然键的唯一键约束/索引的目的是 DBMS 将强制执行该键标识的记录的唯一性 - 您不是假设现实世界的数据完整性,而是在数据库中强制执行它。” - user359040
@马克,大卫,我明白你们关于数据完整性的担忧。但无论如何确保数据完整性,添加代理键是否会造成任何损害? - Yarin
显示剩余4条评论

3

简单回答:它不是关系型的。

该记录(由GUID定义)可能是唯一的,但不能说其中任何一个相关的属性是唯一地发生在该记录中。

使用GUID(或任何纯代理键)与将平面文件声明为关系型没有区别,因为每个记录可以通过其行号进行标识。


1
一个可能很重要但常常被忽视的原因是,如果你将来可能需要与Oracle数据库兼容。
由于Oracle没有唯一标识列数据类型,当你在两个不同的数据库中拥有两种不同的主键数据类型时,尤其是涉及ORM时,这可能会导致一些麻烦。

有趣- 那么Oracle使用整数作为所有主键? - Yarin
我相信 Oracle 可以使用您喜欢的任何内容作为主键...只是他们没有那种特殊类型来原生地表示 GUID。 - Coxy

1

我想知道为什么没有标准的“miniGUID”类型?在GUID上执行一个不错的哈希应该会产生一个64位数字,在任何宇宙中都有微不足道的重复概率,除非它有十亿个或更多的事物。由于大多数GUID/miniGUID标识符使用的宇宙永远不会超过一百万件事物,更不用说十亿了,因此我认为一个更小的8字节miniGuid将非常有用。

当然,这并不意味着它应该用作聚集索引;那样会严重影响性能。尽管如此,与4字节索引相比,8字节的miniGUID只会浪费三分之一的空间。


0

我可以理解为给定应用程序或企业自己的标识符在所有其自己的域中以一致的方式表示并保持唯一性(即因为它们可能跨越多个数据库),但是对于这些目的来说,GUID过于复杂了。我猜它们很受欢迎,因为它们可以直接使用,而设计和实现“企业键”需要时间和精力。设计人工标识符的规则是尽可能简单,但不要太简单。IDENTITY太简单了,GUID不够简单。

存在于应用程序/企业之外的实体通常具有自己的标识符(例如汽车具有VIN,书籍具有ISBN等),由外部可信源维护,在这种情况下,GUID没有任何作用。因此,我想表达的哲学论点是,在每个表上使用人工标识符是不必要的。


1
我建议不要过分依赖外部标识符。第一次你需要输入那些没有VIN编号的被盗车辆或者那些没有ISBN编号的自出版“书籍”,你就会陷入“自己编造VIN、ISBN等标识符”的滑坡之中。 - Peter Stuer
如果自行出版的“书籍”具有与已出版的书籍不同的标识符,则它们将不存在于同一基本表中(可能合并在一个“视图”中)。 - onedaywhen
@Peter Stuer: 人造标识符有哪些情况可以使用吗?是的。每个表格都需要人造标识符吗?不需要。 - onedaywhen
1
我要给这个帖子点个踩(算了,我不能,声望不够),因为你在做关于域完整性的假设,而这些假设在现实世界中并不适用。假设一个记录只会在其当前域内被引用是不切实际的。 - Yarin
@Yarin:我想你没有理解我的观点:如果实体在应用/实体之外需要被识别,那么它要么已经有一个由可信源维护的UPC / EAN / ISBN / VIN类型的通用标识符,因此GUID是多余的,要么你的应用程序将成为可信源,因此GUID不是密钥的最佳格式。 - onedaywhen
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接