何时您真正被迫在设计中使用UUID?

140
我真的看不出UUID的意义所在。UUID的碰撞概率虽然可以说是微乎其微,但微乎其微远远不等于不可能发生。
请问有人能举出一种不得不使用UUID的例子吗?从我看到的所有用途中,我都可以看到没有UUID的替代设计。当然,这个设计可能会稍微复杂一些,但至少它没有非零的失败概率。
对我来说,UUID就像全局变量一样。全局变量使得很多设计变得更简单,但这只是懒惰的设计方法。

26
每件事情都有失败的可能性,我建议关注更有可能发生的问题(也就是你能想到的几乎所有问题),而不是 UUID 碰撞。请注意,这里的“UUID”指通用唯一识别码。 - DanSingerman
16
“实际上,‘实质上为零’非常接近于不可能。” - mqp
21
不,事实上它离不可能无限远。 - Pyrolistical
35
当你开始使用“无限大”这样的词汇时,你已经离开了软件开发的领域。 计算机科学理论是完全不同的讨论,与编写实际软件有很大区别。 - Rex M
3
我会尽可能地关闭,主要是因为git的sha1让我相信哈希的好处。 - Pyrolistical
显示剩余4条评论
16个回答

673

我为Ruby编写了UUID生成器/解析器,因此我认为自己在这个领域相当熟悉。UUID有四种主要版本:

第4版UUID基本上只是从加密安全的随机数生成器中提取出16个字节的随机数据,并进行一些位操作来标识UUID版本和变体。虽然它们极不可能发生碰撞,但如果使用了伪随机数生成器或者你非常非常非常非常非常倒霉,还是有可能会碰撞的。

第5版和第3版UUID分别使用SHA1和MD5哈希函数,将命名空间与已经唯一的数据结合起来生成UUID。例如,这将允许您从URL生成UUID。仅当底层哈希函数也具有冲突时,才可能发生碰撞。

第1版UUID是最常见的。它们使用网络卡的MAC地址(除非欺骗,否则应该是唯一的),加上时间戳,再加上通常的位操作来生成UUID。对于没有MAC地址的机器,6个节点字节是使用加密安全的随机数生成器生成的。如果两个UUID连续生成得足够快以使时间戳匹配前一个UUID,则时间戳将增加1。除非发生以下情况之一,否则不应该发生碰撞:MAC地址被欺骗;运行两个不同UUID生成应用程序的一台机器在完全相同的时刻产生UUID;两台没有网络卡或没有用户级访问MAC地址的计算机得到相同的随机节点序列,并在完全相同的时刻生成UUID;我们用完了表示时间戳的字节并回滚到零。

实际上,这些事件在一个应用程序的ID空间内不会意外发生。除非您接受像Internet范围内那样的ID,或者在不受信任的环境中,恶意个体可能会在ID冲突的情况下做一些坏事,否则就不必担心这个问题。重要的是要理解,如果你生成了与我相同的版本4 UUID,在大多数情况下,这并不重要。我在完全不同的ID空间中生成了这个ID,因此我的应用程序永远不会知道这个冲突,所以这个冲突并不重要。说实话,在没有恶意行为者的单个应用程序空间中,即使每秒生成相当多的UUID,即使使用版本4 UUID,也会在ID冲突之前很久出现地球上所有生命的灭绝。

另外,2 ^ 64 * 16等于256艾字节。也就是说,在一个应用程序空间内存储256 exabytes的ID之前,您需要有50%的机会发生ID碰撞。


2
@Chamnap 我写了UUIDTools。UUID可以转换为整数或原始字节形式,并且作为二进制文件会更小。 - Bob Aman
3
如果发生这种情况,请告诉我。 - Bob Aman
4
1990年,我在Aegis系统上遇到了12次UUID冲突,最后发现是一个故障的FPU引起的,但是我想让你知道这种情况是可能发生的(尽管在过去30多年的编程中除此之外没有再次发生)。顺便说一句,你的解释很好,现在这篇文章成为了我向人们介绍UUID的参考文章 :) - GMasucci
4
@GMasucci 很好的观点。如果硬件有问题或者有人决定让“/dev/random”返回4,那么所有的赌注都作废了。 - Bob Aman
2
@kqr 你说得完全正确,这就是生日悖论问题。但是对于一个 n 位码来说,生日悖论问题可以简化为 2^(n/2),在这种情况下,如我所述,它等于 2^64。 - Bob Aman
显示剩余11条评论

74
UUID可以帮助你获得唯一标识符,而无需咨询或协调中央机构。如果没有某种受管理的基础设施,要获得这样的东西是一个普遍的问题,UUID解决了这个问题。 根据生日悖论,一旦生成了2^64个UUID,发生UUID冲突的可能性为50%。虽然2^64是一个非常大的数字,但50%的冲突几率似乎太高了(例如,在存在多少UUID之前,才会出现5%的冲突几率-即使这也似乎是太大的概率)。 这个分析存在两个问题: 1. UUID不是完全随机的——UUID具有基于时间和/或位置的主要部分。因此,要实际上发生冲突,冲突的UUID需要在不同的UUID生成器中同时生成。我认为,虽然有合理的机会生成几个UUID在同一时间,但有足够的其他垃圾(包括位置信息或随机位),以使在这个非常小的UUID集合之间发生冲突的可能性几乎为零。 2. 严格来说,UUID只需要在它们可能进行比较的其他UUID集合中是唯一的。如果你正在生成用作数据库键的UUID,则无论在其他邪恶的平行宇宙中是否正在使用相同的UUID来标识COM接口,都没有关系。就像如果在半人马座上还有另一个名为“Michael Burr”的人(或物品)一样不会造成混乱。

1
具体例子?COM/DCE UUIDs - 没有分配它们的机构,也没有人愿意承担责任和/或没有人希望有一个机构。分布式数据库没有可靠的链接和没有主服务器。 - Michael Burr
3
更具体的例子是银行应用程序。它安装在多个数据中心中,每个国家都有一个数据中心,每个数据中心都有一个数据库。多个安装是为了遵守不同的法规。每个客户只能在整个集合中有一条客户记录。 - Vineet Reynolds
你需要一个中央服务器来生成客户ID,以便进行整体报告和跟踪(在所有安装上),或者让单个安装生成UUID作为客户ID(显然,在报告中不能使用UUID)。 - Vineet Reynolds
当你有50%的重复机会时,你已经处于被淹没的状态。有人指出了需要多大的体积才能达到0.0000001%的机会。从1开始到n,每次增加n的多个自动增量数据库可以有效地解决相同的问题。 - Gordon
他要求一个音乐会的例子。就像在Phish音乐会上,有另一个人穿着你的衬衫。你希望他们能够销售独特的衬衫。我不明白的是为什么他们不使用你进入音乐会时得到的唯一票号(sessionid的md5值)。 (是的,这很老:P) - Kevin
2
重复的概率比中央机构在某些关键任务上失败的概率要低得多。 - std''OrgnlDave

38

任何事物都有失败的可能性。我建议将注意力集中在更有可能发生的问题上(即你能想到的几乎所有问题),而不是UUID碰撞。


根据Pyrolistical的要求添加为答案 - DanSingerman

17

强调“合理”或者像你所说的“有效”的重要性:足够好是现实世界的运作方式。在“实际上唯一”和“真正唯一”之间填补差距所需要的计算工作量非常大。唯一性是一条随着回报递减的曲线。在曲线上某些点上,存在一条分界线,即“足够独特”仍然是可以负担得起的,然后我们会急剧下降。添加更多的唯一性成本非常高。无限唯一性的代价是无穷大。

UUID / GUID是相对而言计算速度快且容易生成的ID的一种方法,可以合理地假定为全球唯一的标识符。这在许多需要整合来自先前不相关系统数据的系统中非常重要。例如:如果您有一个内容管理系统在两个不同平台上运行,但在某些时候需要将一个系统的内容导入另一个系统中。您不希望ID发生变化,因此从系统A中引用的数据与数据B中创建的数据保持完整,但您也不希望数据发生冲突。UUID解决了这个问题。


解决方案。不要懒惰,更新引用。做正确的事情。 - Pyrolistical
8
这与懒惰无关 - 如果政策规定某个物品的ID被认为是永久且不可变的,那么该ID不会改变。因此,您希望从一开始就使ID唯一,并且希望在不需要从一开始就连接所有系统的情况下实现这一点。 - Michael Burr
你需要上下文。如果你有两组可能会冲突的唯一标识符,你需要更高层次的上下文来将它们区分开来。 - Pyrolistical
24
或者,你可以构建一个使用UUID的系统,然后出售它、赚取一百万美元而且永远不会收到任何关于ID冲突的投诉,因为这种情况不会发生。 - Rex M

16

无需绝对必要创建UUID。然而,有一个标准可以使“离线”用户生成某个键,并具有非常低的碰撞概率。

这有助于数据库复制解决等问题...

对于“在线”用户来说,为某些东西生成唯一键很容易,且不会出现额外负担或者可能性冲突,但这并不是UUID的用途。

无论如何,关于碰撞概率,摘自维基百科:

为了说明这些数字,人类每年被流星撞击的风险被估计为1/170亿,相当于在一年内创建数万亿个UUID并具有1个重复的几率。换句话说,只有在未来100年中每秒生成10亿个UUID,才有约50%的概率创建一个重复的UUID。


4
不要让离线用户生成密钥。在系统上线之前,分配临时密钥,直到真正的密钥生成为止。 - Pyrolistical
在我看来,这是一个非常有帮助的回答...我本来想给出一些与概率相似的比喻,因为似乎 OP 没有完全理解它的意义,但你已经做到了。 - Noldorin
我很清楚概率实际上是微乎其微的。对我来说,使用UUID是一种懒惰的设计,我只是想看看你是否总是可以避免它。 - Pyrolistical
这是公平的,只要您认识到在最极端的情况下需要考虑低概率事件,我就会假设您已经意识到了。 - Noldorin

14

你身体中的每个粒子同时通过你正在坐的椅子进行隧道效应并突然发现自己坐在地板上的可能性是存在的,但概率不为零。

你会担心这个吗?


9
当然不行,那不是我能控制的事情,但设计是可以的。 - Pyrolistical
7
@Pyrolistical,你真的,我是说真的不担心那个吗?那你很奇怪。而且,你是不正确的。你是可以控制它的。如果你增加几磅,就会显著降低这种情况发生的概率。那么,你认为应该增重吗? :-) - Veky

13

一个经典的例子是当你在两个数据库之间进行复制时。

数据库(A)插入一条记录,ID为10,同时数据库(B)创建了一个ID为10的记录。这就是冲突。

使用UUID将不会发生这种情况,因为它们不会匹配。(几乎可以肯定)


2
使用三个数据库,开玩笑地使用3倍。 - Jhonny D. Cano -Leftware-
21
如果你使用2/3或其他倍数,当你后来加入一个新服务器时会发生什么?你必须协调一个切换操作,以便在新服务器上使用n+1倍数,并将所有旧服务器移动到新算法上,你必须在执行此操作时关闭所有内容,以避免在算法切换期间发生冲突。或者...你可以像其他人一样使用UUID。 - Bob Aman
4
事情甚至比那还要糟糕,因为你如何区分2的倍数和4的倍数?或者3的倍数和6的倍数?实际上,你只能使用质数的倍数。噫!只需使用UUID,它有效。微软、苹果和无数其他公司都依赖它们并信任它们。 - sidewinderguy
2
@sidewinderguy,我们信任GUID! :) - Ron Klein
1
@RonKlein是的,愿全球唯一标识符(GUID)祝福我们;-) - sidewinderguy
显示剩余4条评论

9
我有一个避免使用UUID的方案。在某个地方设置一台服务器,每当某个软件需要通用唯一标识符时,它们都会联系该服务器并获取一个。简单明了!但是,即使我们忽略恶意,这种方法也存在一些实际问题。特别是,该服务器可能会故障或从互联网的某些部分无法访问。处理服务器故障需要复制,而这很难做到(请参阅Paxos算法文献以了解为什么共识构建很麻烦),而且速度也相对较慢。此外,如果所有服务器都无法从特定的 "网络 "部分访问,则连接到该子网的 所有 客户端都将无法执行任何操作,因为它们都在等待新的ID。

因此...使用一个简单的概率算法来生成它们,这在地球寿命期间不太可能失败,或者(筹集资金并)构建一个主要基础设施,这将是一个部署PITA并经常出现故障的过程。我知道我会选择哪一个。

3
实际上,UUID的发明初衷就是为了避免您所采用的方法。如果您研究UUID的历史,您会发现它源于早期创建复杂和有意义的计算机网络的实验。人们知道网络本质上不可靠和复杂。UUID是回答如何在多台计算机之间协调数据的问题的答案,即使您知道它们不能始终保持通信。 - Basil Bourque
7
我在第一段使用了讽刺,以防不明显。 - Donal Fellows

6
我不理解所有关于碰撞可能性的讨论。我不在乎碰撞。但我关心性能。

https://dba.stackexchange.com/a/119129/33649


UUIDs在非常大的表中性能很差。(20万行不算“非常大”)。当字符集为utf8时,您的第三个方法非常糟糕--CHAR(36)占用108个字节!UUID(GUID)非常“随机”。在大表上将它们用作UNIQUE或PRIMARY键非常低效。这是因为每次插入新的UUID或按UUID选择时都必须跳转到表/索引周围。当表/索引太大而无法放入缓存中(请参见innodb_buffer_pool_size,它必须小于RAM,通常为70%),则可能不会缓存“下一个”UUID,因此速度慢的磁盘命中。当表/索引比缓存大20倍时,只有1/20(5%)的命中被缓存--您受到I/O限制。因此,除非具有“小”表或确实需要它们以便从不同位置生成唯一ID(并且尚未找出另一种方法),否则不要使用UUID。有关UUID的更多信息:http://mysql.rjweb.org/doc.php/uuid(它包括在标准36个字符UUID和BINARY(16)之间转换的函数)。在同一表中同时具有UNIQUE AUTO_INCREMENT和UNIQUE UUID是浪费的。发生INSERT时,必须检查所有唯一/主键是否重复。任何唯一键都足以满足InnoDB对具有主键的要求。BINARY(16)(16字节)有些臃肿(反对将其作为PK的论据),但并不那么糟糕。当您拥有二级键时,臃肿性很重要。InnoDB在每个次要键的末尾悄悄地附加了PK。这里的主要教训是尽量减少二级键的数量,特别是对于非常大的表。相比之下:INT UNSIGNED是4个字节,范围为0..40亿。BIGINT为8个字节。

4

如果你只看看其他选项,例如对于一个简单的数据库应用程序,在创建新对象之前每次查询数据库,你很快就会发现使用UUID可以有效地减少系统的复杂性。当然,如果你使用32位的int键,它将在128位UUID的四分之一中存储。不可否认,UUID生成算法需要比简单递增数字更多的计算能力。但是,谁在乎呢?管理“授权”以分配其他唯一数字的开销轻易超过了这个数量级,具体取决于您打算使用的唯一ID空间。


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