标识符指南

UUID v1、v4、v7 与命名空间版本:该选哪一个

UUID 的 128 位是怎么排布的、每个版本各自让出了什么、随机主键为什么伤索引,以及一个 UUID 会泄露它在何时何地被生成。

UUID 是 128 位,有固定的文本形状,版本号就刻在里面。各版本并不能互换:它们填充这些比特的方式不同,而这个差别决定了你的标识符是否按创建时间有序、是否会拖垮索引、以及会向看到它的人透露什么。

这 128 位里到底装了什么

每个 UUID 都是 128 位,以 8-4-4-4-12 分组打印为 32 位小写十六进制。其中两个小字段由规范固定,生成器不能随意使用。第三组的第一个字符是版本位;第四组的第一个字符是变体位,你会遇到的 UUID 里它一定是 8、9、a 或 b 之一——这些取值的高两位是 10,标记的正是 RFC 布局,以区别于更老的微软 GUID 布局和 NCS 布局。

剩下 122 位由各版本自由填充。版本 4 全部填随机数据。版本 7 用前 48 位放毫秒时间戳,其余 74 位随机。版本 1 用 60 位放时间戳、14 位放时钟序列、48 位放节点标识。版本 3 和 5 用哈希结果填充。所谓「选版本」,全部内容就是这个。现行规范是 2024 年 5 月发布的 RFC 9562,它取代了 RFC 4122 并把版本 6、7、8 标准化——因此把 v4 说成「现代场景唯一合理选择」的资料,都早于它。

不用解析器也能读懂一个 UUID

UUID 的版本可以一眼看出来,当你盯着日志、纳闷自己的标识符为什么排不出序时,这是一项有用的技能:数到第 14 个十六进制字符,也就是第二个连字符后的第一个字符,那就是版本。

下面这个是真实的 v7 值,它的时间戳字段能解出一个确切时刻。要注意 v7 只精确到毫秒有序。同一毫秒内生成的两个值没有定义顺序,除非实现使用了 RFC 9562 允许的、位于随机字段中的可选单调计数器——而并非所有实现都用。如果你需要严格全序,UUID 就不是合适的原语,请用数据库序列。

定位版本位并解码 v7 时间戳
01a0c2c9-ef28-7c3a-9d41-6f2b8e57a104
                ^         ^
                |         +-- 变体位:9(二进制 10xx = RFC 布局)
                +------------ 版本位:7

前 48 位 = 0x01a0c2c9ef28 = 1789974409000
  自 Unix 纪元起的毫秒数
  = 2026-09-21T07:06:49.000Z

对比一个 v4,相同位置什么都不承载:
  9b2f4e1c-88d0-4a6b-b7e5-1c3f90ad2e44
                    ^     ^
                    |     +-- 变体位:b
                    +-------- 版本位:4
  122 个非固定位全部随机;没有时间,也没有顺序

版本横向对比

下表是把决策压缩成一张表。先看最后一列,它通常就能结束争论。表中略去了版本 2 和 8,因为你几乎肯定用不上:v2 是基本无人使用的 DCE Security 变体,v8 则是一个刻意留空的槽位,给那些只是想借用 UUID 文本形状的自定义布局。

有一列需要补一句说明。这里的「按时间有序」指的是按取值的原始字节排序,这也正是 v1 被标为不有序的原因:它的时间戳是低位字段在前存放的,因此相隔几秒生成的两个 v1 值,排序结果可能是任意的。

版本比特如何填充是否按时间有序什么时候用
v160 位时间戳 + 14 位时钟序列 + 48 位节点标识(历史上是 MAC 地址)字典序不有序——时间戳是低位在前存放的必须与既有 v1 系统互通时。新项目不要选它。
v3命名空间 UUID 加名称的 MD5 哈希否需要确定性 ID,且必须与既有 v3 部署保持一致。
v4来自 CSPRNG 的 122 位随机数据否通用标识符:顺序无所谓,且完全不希望带任何时间信号。
v5命名空间 UUID 加名称的 SHA-1 哈希否同样的输入必须永远产生同样的 UUID。新项目优先于 v3。
v6把 v1 的字段重排,让时间戳高位在前是正在从 v1 迁移,同时需要保留它的节点与时钟字段。
v748 位 Unix 毫秒时间戳,随后 74 位随机是,精确到毫秒数据库主键、事件 ID,以及一切按时间顺序写入的数据。新系统的默认选择。

v4 与「会不会撞」这个问题

关于随机标识符,反复出现的担忧是重复。值得把算术做一遍,之后就可以不用再担心了。122 个随机位对应的空间约为 5.3 × 10^36 个取值。碰撞概率遵循生日界:大约等于 n² 除以两倍空间大小,其中 n 是你已经生成的数量。

生成十亿个 v4,任意两个相撞的概率约为 9 × 10^-20。生成一百万亿个——也就是 10^14,超过任何单一系统会产出的量级——概率约为十亿分之一。要让「出现一次碰撞」的概率达到掷硬币的水平,大约需要 2.7 × 10^18 个 UUID,相当于每秒生成十亿个、连续生成一个世纪。

重要的前提是:这套算术假设随机源是密码学安全的。用 Math.random 生成、用容器启动时熵池为空的劣质 PRNG 生成、或者用测试桩里的固定种子然后一路泄漏到生产环境——这些都足以把 v4 废掉。现实中报告出来的 v4 碰撞,原因基本上永远是生成器,而不是生日界。请使用平台的 CSPRNG:浏览器和 Node 用 crypto.randomUUID,Python 用 secrets 或 uuid4,Go 用 crypto/rand。

随机主键为什么伤性能,v7 又修好了什么

这正是 v7 存在的实际理由。多数关系型数据库把表数据或索引存在按键排序的 B 树里。按键递增插入意味着每一条新记录都落在最右侧的叶页上,那一页本来就在内存里,写满之后才分配新页。按随机键插入则意味着每次插入都命中任意一页,于是脏页工作集是整棵索引,而不是它的尾部。

v7 键恢复了递增写入的模式,同时保留 74 个随机位,这个量级足以让标识符在任何实用意义上都不可猜测。你还顺带得到一棵天然按创建时间排序的索引,于是针对近期时间窗的范围查询只会触及一段连续区域,而不是整棵树。

有两个存储细节比选版本更要紧。第一,把 UUID 存成 16 个原始字节——BINARY(16)、uuid、RAW(16)——而不是 36 个字符的字符串,后者会让键宽度翻一倍还多,并且在每一个二级索引上被放大。第二,如果你用 MySQL,请确认驱动的字节序:MySQL 自带的 UUID_TO_BIN(uuid, 1) 会交换 v1 的时间字段使其可排序,这对 v1 是对的,对 v7 则是错的——v7 的字节本来就已经是正确顺序。

一个 UUID 会告诉看到它的人什么

版本 1 是真正有「惊喜」历史的那一个。它的节点字段最初被规定为机器的 MAC 地址,很多实现至今仍在使用,因此一个 v1 UUID 可以标识出生成它的那台物理机器,以及精确到 100 纳秒的生成时刻。现代库常常改用随机节点,但你从外观上分辨不出来。对来源不明的 v1 值,一律按「携带时间戳、可能还携带硬件地址」对待。

版本 7 会披露创建时间戳,而且是刻意且精确地披露。这通常无害——对象一般本来就有 created_at 字段——但如果创建时间本身就是秘密,就要三思:未公开的分享链接、密码重置令牌、发布前的记录,都属于这一类。它还让标识符在时间维度上具备弱可枚举性:拿到你两个 ID 的人能知道这两者之间隔了多久(至于这期间签发了多少个,v7 并不泄露,除非你自己把计数器暴露出去)。

版本 3 和 5 不直接披露任何东西,但它们是确定性的,也就是说:能猜到输入、又知道命名空间的人,就能自己算出这个 UUID。uuid5(NAMESPACE_DNS, "example.com") 对所有人、在任何时候,永远是 cfbff0d1-9375-5685-968c-48ce8b15ae17。这正是它适合做去重和幂等键的原因,也正是它绝不能在低熵输入(比如邮箱地址或自增 ID)之上被当作能力令牌使用的原因。

怎么选,以及生成器能做和不能做什么

一句话版本:存数据库或按时间排序的用 v7;明确不希望带时间信号的用 v4;同样输入必须映射到同样标识符的用 v5;只有被既有系统逼着的时候才用 v1。

本站的 UUID 生成器在浏览器里用平台的密码学随机源产出取值,不向任何地方发送数据。它的边界同样值得说清楚:生成出来的标识符,「唯一」只在上文那套算术的意义上成立,而你数据里的唯一性,靠的仍然是唯一约束,而不是这个格式。

最后,不要把 UUID 当作访问控制手段。一个不可猜测的标识符可以作为合理的第二层防护,但含有 v4 UUID 的 URL 照样会出现在 referrer 头里、被粘进聊天窗口、在页面公开时被搜索引擎收录、并长期留存在服务端日志中。请先对请求做鉴权,再用这个 UUID 去查行。

要点回顾

  • 数据库主键和一切按时间写入的数据,默认选 v7:它精确到毫秒有序,同时仍带 74 个随机位。
  • 不想要时间信号时用 v4,同样输入必须产生同样标识符时用 v5,只为兼容既有系统时才用 v1。
  • 现实中的 v4 碰撞来自弱随机源而非生日界——永远用平台的 CSPRNG 生成。
  • 把 UUID 存成 16 个原始字节而非 36 字符串,并在对 v7 值套用 v1 时代的字节交换之前先确认驱动行为。
  • 默认拿到 v1 的人能读出创建时间、可能还有 MAC 地址;拿到 v7 的人能读出创建时间。

继续了解相关检查与工具