在Python中,何时应使用uuid.uuid1()和uuid.uuid4()?

264

我从文档中了解到两者之间的区别。

uuid1()
使用主机ID、序列号和当前时间生成UUID。

uuid4()
生成随机UUID。

因此,uuid1使用机器/序列/时间信息来生成UUID。使用每个方法有什么优缺点呢?

我知道uuid1()可能存在隐私问题,因为它是基于机器信息的。我想知道在选择这两种方法时是否存在任何微妙的差别。目前我只使用uuid4(),因为它是完全随机的UUID。但我想知道是否应该使用uuid1来减少冲突的风险。

基本上,我正在寻找人们在使用其中一种方法时的最佳实践技巧。谢谢!


3
这里有一个替代UUID的方法。虽然UUID发生冲突的概率微乎其微,但并不能保证唯一性。为了确保唯一性,你可以使用复合键[<系统ID>, <本地ID>]。每个参与数据共享的系统必须具有自己独特的系统ID,这可以在系统设置期间分配,也可以从公共池中获取。本地ID是任何特定系统内的唯一标识符。尽管这更麻烦些,但可以保证唯一性。抱歉离题了,只是想帮忙。 - oᴉɹǝɥɔ
3
不关心他提到的“隐私问题”。 - Shrey
6个回答

307

uuid1() 保证不会产生冲突(假设你不同时创建太多)。 如果重要的是uuid和计算机之间没有任何联系,我就不会使用它,因为mac地址被用来使其在计算机之间唯一。

如果在100ns内创建了超过2的14次uuid1,则可能会创建重复项,但这对大多数用例来说不是问题。

uuid4()生成随机UUID。碰撞的几率非常小,小到你不必担心。问题在于,糟糕的随机数生成器会增加发生碰撞的概率。

Bob Aman的这个优秀答案很好地总结了这一点。(我建议阅读整个答案。)

坦白说,在一个没有恶意行为者的单个应用程序空间中,即使您每秒生成相当多的UUID,即使在版本4 UUID上,您的碰撞也将在地球上所有生命灭绝之前发生。


1
@gs 是的,与我阅读的内容相符。uuid1更加独特,而uuid4则更加匿名。因此,除非有理由不使用它,否则基本上使用uuid1。@mark ransom: 很棒的答案,在我搜索uuid1/uuid4时没有出现。看起来直接从权威来源得知。 - rocketmonkeys
7
如果在同一个节点上每秒钟生成多个uuid1,则不一定会产生唯一的UUID。例如:[uuid.uuid1() for i in range(2)]。当然,除非发生了我没有注意到的奇怪情况。 - Michael Mior
6
@Michael:uuid1有一个序列号(在你的示例中是第四个元素),因此除非你使用计数器中的所有位,否则不会发生任何冲突。 - Georg Schölly
我本应该进行测试的。它们只是碰巧看起来一样。但是我之前曾经遇到过类似上面代码片段且数字大于2的碰撞问题。 - Michael Mior
3
@Michael: 我尝试研究碰撞发生的情况,并添加了我找到的信息。 - Georg Schölly
显示剩余2条评论

44

我团队在数据库升级脚本中使用UUID1时遇到了麻烦,我们在几分钟内生成了约12万个UUID。UUID冲突导致了主键约束的违反。

我们已经升级了数百台服务器,但在我们的Amazon EC2实例中有几次遇到了这个问题。我怀疑是时钟分辨率问题,转换为UUID4后我们解决了这个问题。


39

在涉及多台机器进行扩展的情况下,例如处理多个在线交易时,您可能会考虑使用uuid1()而不是uuid4()。在这种情况下,由于伪随机数生成器初始化方式选择不当等原因,发生冲突的风险以及生成更多UUID的可能性增加,导致创建重复ID的可能性更大。

在这种情况下,uuid1()的另一个优点是每个GUID最初生成的机器会被隐式记录(在UUID的“节点”部分)。这和时间信息可以帮助调试。


126位真随机数碰撞的概率非常低,实际上低到我认为这并不重要。 - René

10

使用uuid1时需要注意,如果不指定clock_seq参数并使用默认调用,则可能会遇到碰撞的情况:您只有14位随机性(在100纳秒内生成18个条目,存在大约1%的碰撞几率,请参见生日悖论/攻击)。在大多数情况下,这个问题不会发生,但在具有较差时钟分辨率的虚拟机上可能会遇到此问题。


7
@Guillaume,看到使用clock_seq的良好实践示例会非常有用... - eric
@Guilaume,你是如何计算出这1%的概率的?14位随机数意味着如果你在100纳秒内生成了>= 2^14个ID,则碰撞将肯定发生,这意味着当你每100纳秒产生大约163个ID时,就有1%的碰撞几率。 - maks
1
@maks 就像我说的那样,你应该看一下生日悖论 - Guillaume

7
也许没有提到的一点是本地性。
MAC地址或基于时间排序的UUID1可以提高数据库性能,因为对于排序更接近的数字而言,比随机分布的数字(UUID4)所需的工作量要少(请参见此处)。
第二个相关问题是,在调试中使用UUID1可能很有用,即使原始数据丢失或未明确存储(这显然与OP提到的隐私问题相冲突)。

6
除了被接受的答案外,有第三种选项在某些情况下可以很有用:
v1带随机MAC地址(“v1mc”)
您可以通过故意生成具有随机广播MAC地址的v1 UUID(这是v1规范允许的)来在v1和v4之间进行混合。生成的v1 UUID取决于时间(就像常规v1),但缺乏所有特定于主机的信息(如v4)。它的冲突抗性也更接近于v4:v1mc = 60位时间 + 61位随机位 = 121位唯一位;v4 = 122位随机位。
我第一次遇到这个问题是在Postgres的uuid_generate_v1mc()函数中。此后,我使用了以下Python等效代码:
from os import urandom
from uuid import uuid1
_int_from_bytes = int.from_bytes  # py3 only

def uuid1mc():
    # NOTE: The constant here is required by the UUIDv1 spec...
    return uuid1(_int_from_bytes(urandom(6), "big") | 0x010000000000)
< p > < em >(注意:我有一个更长和更快的版本,可以直接创建UUID对象;如果有人需要,可以发布) (Note: 我有一份更长、更快的版本,可以直接创建UUID对象;如果有需要,我可以发布。)

如果每秒的通话量很大,这有可能耗尽系统的随机性。你可以使用stdlib中的random模块(它可能也会更快)。但要注意:在生成数百个UUID之后,攻击者就可以确定RNG状态,从而部分预测未来的UUID。

import random
from uuid import uuid1

def uuid1mc_insecure():
    return uuid1(random.getrandbits(48) | 0x010000000000)

似乎这种方法就像v4(主机无关),但比它更糟糕(比特少,依赖于urandom等)。与只使用uuid4相比,它有哪些优势? - rocketmonkeys
这主要是一个升级版本,适用于那些需要时间特性的情况,但需要更强的碰撞抗性和主机隐私。一个例子是作为数据库的主键 - 与v4相比,v1 uuid在写入磁盘时具有更好的局部性,具有更有用的自然排序等。但是,如果您有一个攻击者预测2 ** 61位是安全问题(例如作为uuid nonce),那么肯定要使用uuid4(我知道我会这样做!)。关于使用urandom而变得更糟糕,我不确定您的意思 - 在Python下,uuid4()也使用urandom。 - Eli Collins
好东西,很有道理。不仅要展示你的代码能做什么,还要说明为什么需要它。关于urandom,我的意思是你正在消耗两倍的随机性(一个用于uuid1,另一个用于urandom),因此可能更快地耗尽系统熵。 - rocketmonkeys
实际上,它只有uuid4的一半大小:uuid1()使用14位用于clock_seq,这会舍入为2个字节的urandom。uuid1mc包装器使用48位,应该映射到6个字节的urandom,每次调用总共消耗urandom(8)。而uuid4直接为每个调用调用urandom(16)。 - Eli Collins
显然,许多托管提供商(例如AWS)对大量托管的虚拟机使用相同的MAC地址。基于容器的部署,如Kubernetes,似乎也存在类似的问题。在这种情况下,你可能希望采用“随机MAC UUID1”方法,为每个容器/实例生成一个随机(静态?)的MAC地址,假设你想要UUID1的“时间局部性”属性,而不是完全随机的UUID4。这样合理吗? - shadowtalker
另外,我找到了RFC 4122的相关部分:4.5.不标识主机的节点ID - shadowtalker

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