什么时候真正被迫使用 UUID 作为设计的一部分?

我看不出 UUID有什么意义。我知道碰撞的概率是 实际上是零,但是 实际上是零并不是不可能的。

有没有人能举个例子,说明你别无选择只能使用 UUID?从我所看到的所有用途中,我可以看到一种没有 UUID 的替代设计。当然,设计可能会稍微复杂一点,但至少它不会有非零的失败概率。

UUID 对我来说就像是全局变量。全局变量有许多方法可以使设计更简单,但它只是懒惰的设计。

46389 次浏览

在我的上一份工作中,我们从第三方获得了唯一标识为 UUID 的对象。我放入了一个 UUID-> 长整数查找表,并使用长整数作为主键,因为这样快得多。

一个典型的例子是在两个数据库之间进行复制。

DB (A)插入一条带有 int ID 10的记录,同时 DB (B)创建一条带有 ID10的记录。这是碰撞。

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

强调“合理的”或者如你所说的“有效的”: 足够好就是现实世界的运作方式。在弥补“实际上独一无二”和“真正独一无二”之间的差距所涉及的计算工作量是巨大的。唯一性是有报酬递减的曲线。在曲线的某一点上,在“足够独特”仍然可以负担得起的地方之间有一条线,然后我们曲线非常陡峭。添加更多惟一性的成本变得相当大。无穷的唯一性有无穷的代价。

相对而言,UUID/GUID 是一种计算上快速而简单的方法,可以生成一个假定为普遍唯一的 ID。这在许多需要集成来自先前未连接系统的数据的系统中非常重要。例如: 如果您有一个内容管理系统,它运行在两个不同的平台上,但是在某个时候需要将内容从一个系统导入到另一个系统中。您不希望 ID 发生更改,因此系统 A 中数据之间的引用保持不变,但是您不希望与系统 B 中创建的数据发生任何冲突。UUID 可以解决这个问题。

创建 UUID 从来都不是绝对必要的。然而,有一个标准是很方便的,在这个标准中,离线用户每个人都可以生成一个碰撞概率非常低的密钥。

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

在线用户很容易为没有开销或冲突可能性的东西生成唯一的密钥,但这不是 UUID 的用途。

无论如何,关于碰撞概率的一个词,来自维基百科:

为了正确地看待这些数字,一个人每年遭受打击的风险 被陨石击中的几率估计是170亿分之一,相当于 在一年内制造出几十万亿个 UUID 的几率 只有一个复制品。换句话说,只有在产生了10亿个 UUID 在未来100年的每一秒,创建的概率 只有一个复制品大约是50% 。

使用版本1算法,在同一个 MAC 地址每毫秒生成的 UUID 少于10个的约束条件下,似乎不可能发生冲突

从概念上讲,原版(版本1) UUID 的生成方案是 将 UUID 版本与 计算机的 MAC 地址 生成 UUID,并使用 100纳秒间隔数 自从格里高利书颁布以来 实际上,西方的日历 实际的算法更复杂。 这个计划在2000年受到了批评 它不够“不透明”; 它揭示了 生成 UUID 和 它这样做的时间。

如果我误解了它的工作原理,请纠正我

UUID 给你带来的东西是非常难以做到的,否则就是得到一个唯一标识符的 不必与中央权威协商或协调。在不使用某种托管基础设施的情况下获取这样的内容的一般问题是 UUID 解决的问题。

根据生日问题,一旦生成了2 ^ 64个 UUID,发生 UUID 冲突的几率是50% 。现在2 ^ 64是一个相当大的数字,但是50% 的碰撞几率似乎太冒险了(例如,在有5% 的碰撞几率之前需要多少 UUID 才能存在——即使这个几率似乎太大了)。

这种分析存在两个问题:

  1. UUID 并不完全是随机的—— UUID 的主要组件是基于时间和/或位置的。因此,为了有任何真正的碰撞机会,碰撞的 UUID 需要从不同的 UUID 生成器同时生成。我想说的是,虽然有一个合理的机会,几个 UUID 的可能会同时生成,有足够的其他粘性(包括位置信息或随机位) ,使冲突的可能性之间的这个非常小的 UUID 集几乎不可能。

  2. 严格地说,UUID 只需要在可以比较的其他 UUID 集合中是唯一的。如果您正在生成一个用作数据库密钥的 UUID,那么在另一个邪恶世界的其他地方是否使用相同的 UUID 来标识 COM 接口并不重要。就像在半人马座阿尔法星上如果有人(或者什么东西)叫“迈克尔 · 伯尔”也不会引起混乱一样。

在 UUID = = 惰性设计上

我不同意你挑起争端。如果一个重复的 UUID 在统计学上是不可能的,而且数学已经被证明了,那么为什么要担心呢?花时间设计围绕您的小 N UUID 生成系统是不切实际的,总有一打其他方法,您可以改善您的系统。

还有一个非零的可能性,就是你身体里的每一个粒子都会同时穿过你所坐的椅子,然后你会突然发现自己坐在地板上。

你担心这个吗?

任何事物都有非零失败的可能性。我将集中讨论比 UUID 冲突更可能发生的问题(即几乎所有您能想到的问题)

我为 Ruby 编写了 UUID 生成器/解析器,因此我认为自己对这个主题相当了解。有四个主要的 UUID 版本:

版本4的 UUID 实际上只是从加密安全的随机数生成器中提取的16字节随机性,通过一些比特调整来识别 UUID 版本和变体。这些极不可能发生碰撞,但是如果使用 PRNG,或者如果你碰巧有非常非常非常非常非常非常非常不幸的运气,这种情况就可能发生。

版本5和版本3的 UUID 分别使用 SHA1和 MD5散列函数,将名称空间与一段已经惟一的数据结合起来生成 UUID。例如,这将允许您从 URL 生成 UUID。这里的冲突只有在基础散列函数也有冲突的情况下才可能发生。

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

实际上,这些事件都不是偶然发生在单个应用程序的 ID 空间中的。除非您在互联网范围内接受 ID,或者在一个不可信的环境中,在这种环境中,恶意用户可能会在 ID 冲突的情况下做一些坏事,否则这不是您应该担心的事情。如果您碰巧生成了与我相同的版本4 UUID,那么理解这一点是至关重要的,在大多数情况下,这并不重要。我在一个完全不同的 ID 空间中生成了这个 ID。我的应用程序永远不会知道碰撞,所以碰撞并不重要。坦率地说,在没有恶意行为者的单个应用程序空间中,地球上所有生命的灭绝将在碰撞发生之前很久就发生,即使是在版本4的 UUID 上,即使您每秒生成相当多的 UUID。

还有,2 ^ 64 * 16是256EB。例如,您需要存储256EB 的 ID,然后才有50% 的机会在单个应用程序空间中发生 ID 冲突。

如果您只考虑其他选择,例如,对于一个简单的数据库应用程序,每次创建新对象之前都必须查询数据库,那么您很快就会发现使用 UUID 可以有效地降低系统的复杂性。当然-如果使用 int 键,则为32位,它将存储在128位 UUID 的四分之一中。授予 UUID 生成算法比简单地递增一个数字占用更多的计算能力。但是,谁在乎呢?管理一个“权威”来分配其他唯一数字的开销很容易超过数量级,这取决于你想要的唯一性 ID 空间。

我有一个避免 UUID 的方案。在某个地方设置一个服务器,这样每当某个软件需要一个 UUID 时,他们就会联系该服务器,然后该服务器就会提供一个。很简单!

除了这里有一些真正的实际问题,即使我们忽略了纯粹的恶意。特别是,该服务器可能会失败,或者在部分互联网上无法访问。处理服务器故障需要进行复制,这就是 非常困难的正确性(请参阅关于 Paxos 算法的文献,了解为什么建立共识非常困难) ,而且复制速度非常慢。此外,如果所有的服务器都不能从网络的特定部分访问,那么连接到该子网的客户机的 没有将能够做任何事情,因为它们都在等待新的 ID。

因此... ... 使用一个简单的概率算法来生成在地球有生之年不太可能失败的数据,或者(基金和)建立一个主要的基础设施,这将是一个部署 PITA 并且经常失败。我知道我会选哪个。

UUID 体现了与全局变量相关的所有糟糕的编码实践,只有更糟糕的,因为它们是可以分布在不同工具包上的超全局变量。

最近遇到了这样一个问题,用一个精确的替换模型更换打印机,发现没有一个客户端软件可以工作。

对于那些说 UUID 是糟糕的设计,因为他们 可以(在一些可笑的小概率)碰撞,而你的数据库生成的密钥不会... 你知道的机会,人为错误导致碰撞你的数据库生成的密钥,因为一些不可预见的需要是远远高于 UUID4碰撞的机会。我们 知道,如果数据库被重新创建,它将再次以1开始 id,我们中有多少人必须重新创建一个表,当我们确信我们永远不会需要的时候?我把我的钱放在 UUID 安全当事情开始出错与未知-未知的任何一天。

我不太明白撞击的可能性。我不在乎什么碰撞。不过我关心的是表现。

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

对于非常大的表,UUID 是一个性能灾难 不是“非常大”)

当字符集 utf8—— CHAR (36)时,您的 # 3非常糟糕 占用了108个字节!

UUID (GUID)是非常“随机”的 大型表上的主键效率非常低 每次插入新的 UUID 时,都必须跳过表/索引 当表/索引太大以至于无法放入缓存时 (参见 inodb _ buffer _ pool _ size,它必须小于 RAM, 通常为70%) ,“下一个”UUID 可能无法缓存,因此磁盘速度较慢 当表/索引比缓存大20倍时,只有1/20 (5%)命中缓存-您是 I/O 绑定的。

因此,不要使用 UUID,除非

你有“小”表,或者你真的需要它们,因为生成 来自不同地方的唯一 ID (而且还没有找到另一种方法 更多关于 UUID 的资料: http://mysql.rjweb.org/doc.php/uuid(It 包括用于在标准36字符 UUID 和 二进制(16)

同时具有 UNIQUE AUTO _ INCREMENT 和 UNIQUE UUID 桌子是浪费。

当发生 INSERT 时,必须检查所有唯一/主键 任何一个唯一的键都足以满足 InnoDB 的要求 二进制(16)(16字节)有点笨重(一个 反对使其成为 PK 的论点) ,但并不那么糟糕 当你有次要的关键。 InnoDB 默默地钉 PK 在每个次要键的末端。这里的主要教训是 最小化辅助键的数量,特别是对于非常大的 对于比较: INTUNSIGNED 为4字节,范围为0. .4 BIGINT 是8字节。

除了必须使用需要 UUID 的其他人的 API 之外,当然总有其他解决方案。但是这些替代方案能够解决 所有和 UUID 一样的问题吗?当您本可以一次性解决所有问题时,您最终是否会添加更多的 Hacks 层,每层解决一个不同的问题?

是的,UUID 在理论上是有可能发生碰撞的。正如其他人指出的那样,它不太可能达到不值得考虑的程度。这种事从来没有发生过,也很可能永远不会发生。算了吧。

避免冲突的最“显而易见”的方法是让单个服务器在每个插入上生成唯一的 ID,这显然会造成严重的性能问题,而且根本不能解决离线生成问题。哎呀。

另一个“显而易见”的解决方案是一个中央权威机构,它提前分发惟一数字块,这基本上就是 UUID V1通过使用生成机器的 MAC 地址(通过 IEEE OUI)所做的事情。但是重复的 MAC 地址确实会发生,因为每个中央机构最终都会搞砸,所以在实践中这比 UUID V4冲突更有可能发生。哎呀。

反对使用 UUID 的最佳理由是它们“太大了”,但是一个(显著的)更小的方案将不可避免地无法解决最有趣的问题; UUID 的大小是它们在解决这些问题时的有用性的固有副作用。

可能您的问题不够大,不需要 UUID 提供什么,在这种情况下,可以随意使用其他东西。但是如果您的问题意外地增长(大多数情况下也是如此) ,您最终将在以后进行切换——并且会为一开始没有使用它们而自责。为什么要为失败而设计,而不是为成功而设计呢?