不喜欢广告? 无广告 今天

UUID 版本详解 — 停止对所有场景都使用 v4

更新于

UUID v4 在所有地方都是默认值,但它会分割你的数据库索引。以下是何时使用 v4、v7 和 ULID —— 以及为何你的数据库管理员会感谢你切换。

UUID 版本详解——停止在所有场景中使用 v4 1
广告 移除?

在你的代码库中,有一行看起来像这样的代码: id = uuid.v4()它能正常工作。你的测试通过了。用户从不抱怨。而每当数据库管理员查看查询计划时,她都会默默不悦。

UUID v4 成为默认选项,是因为它简单、普遍可用,并且碰撞概率足够低,以至于你实际上永远不会遇到冲突。但“它能工作”和“这是正确的工具”是两回事——对于数据库主键而言,v4 在系统扩展时实际上正在使其变慢。

UUID v4 对数据库的实际影响

关系型数据库使用 B 树索引作为主键。B 树会保持数据有序,从而使数据库能在 O(log n) 时间内查找行。当你插入一条新记录时,数据库需要将其放置在索引中的正确排序位置。

对于 UUID v4,每个新 ID 都是随机的。 550e8400-e29b-41d4-a716-446655440000 可能接着是 f47ac10b-58cc-4372-a567-0e02b2c3d479 ——连续插入之间没有关系。数据库每次都要导航到 B 树中的一个随机叶节点。

这在大规模情况下会导致两个问题:

  • 页分裂: 当 B 树的叶节点填满时,数据库必须将其分成两个节点,并更新父节点。对于随机插入,分裂频繁发生在整个索引中,而不仅仅是末尾。
  • 缓存未命中: 数据库缓冲池会将最近使用的页面保留在内存中。顺序插入会持续命中相同的“热点”页面。而随机插入则将读取分散到整个索引中,导致缓存被耗尽,从而强制进行磁盘读取。

对于拥有数百万行且写入吞吐量高的表,这种索引碎片化会导致实际延迟。如果你的 EXPLAIN ANALYZE 显示索引扫描时间比预期长,那么随机 UUID 可能是诊断的一部分。

UUID 家族树

UUID v1:时间戳、MAC 地址,以及我们为何不再使用它

UUID v1 是最初的“可排序”UUID。它编码了一个自1582年10月起的100纳秒时间间隔的60位时间戳,结合一个时钟序列和你机器的 MAC 地址。结果是大致按时间排序的。

MAC 地址部分是导致 v1 被淘汰的原因。它会将你的服务器网络接口标识符泄露到你生成的每一个 ID 中——每一个用户记录、每一个订单、每一个事件。安全研究人员证明,一旦你获得一个样本,来自同一台机器的 v1 UUID 就是可预测的。组织开始避免在面向用户的应用中使用它,大多数 UUID 库也已将其标记为过时。

UUID v4:随机、安全,但不适合用作主键

UUID v4 包含122位的加密随机数据(剩余位编码版本)。对于十亿个 UUID,碰撞概率约为10^18分之一。在实际应用中,可以认为是零。

这种随机性正是你想要的,用于安全令牌、会话 ID、API 密钥和相关 ID——任何需要无法被猜测或枚举的 ID 的场景。对于这些用途,继续使用 v4。问题在于,“无法被猜测”和“数据库友好”是两个相反的属性。

想尝试 UUID 生成? IO Tools 上的 UUID 生成器 可以让你并排生成 v1、v4、v7 及其他变体,以观察其结构差异。

UUID v7:你应该使用的新型默认值

UUID v7 于2023年5月由 IETF 在 RFC 9562 中标准化。它将毫秒级的 Unix 时间戳放入最显著的48位,接着是4位版本字段、12位序列计数器和62位的随机数据。

实际意义是:生成的 UUID 会按字典顺序递增。连续插入会落在 B 树的相邻位置。没有随机分散,没有不必要的页分裂,没有缓存震荡。从数据库的角度来看,它表现得像一个自动递增的整数,同时仍具备全局唯一性,无需协调。

同一毫秒内的序列计数器确保了单调排序,即使在高频生成器中也是如此。如果你在一毫秒内生成10,000个 UUID,它们仍然会正确排序。随机后缀保留了足够的熵,使得分布式系统之间的碰撞概率极低。

对于使用 PostgreSQL、MySQL 或其他关系型数据库的任何新系统,UUID v7 应作为主键的默认选择。

ULID:先出现的替代方案

ULID(通用唯一字典可排序标识符)在 UUID v7 出现之前就解决了同样的问题。它使用48位的毫秒级 Unix 时间戳和80位的随机数据,以 Crockford 的 Base32 编码。

结果是一个26字符的字符串,看起来像 01ARZ3NDEKTSV4RRFFQ69G5FAV 而不是36字符的带连字符的 UUID 格式。它无需编码即可作为 URL 安全,按字符串正确排序,并且是大小写不敏感的。

ULID 没有 IETF RFC——它有一个在 ulid.github.io 上的社区规范。这对大多数团队来说已经足够,但如果在需要正式标准化标识符的监管环境中,UUID v7 是更安全的选择。ULID 在 JavaScript 和 Go 生态系统中拥有强大的库支持,如果你的团队已经使用它,就没有迫切的理由进行切换。

并排比较

财产UUID v1UUID v4 UUID v4(随机 UUID)是一种通过随机数生成的全球唯一标识符,通常用于数据库记录、文件系统和网络应用程序中。它由32位二进制数组合而成,并以“-”分隔为五个部分:8-4-4-12。 **注意:** 此版本的UUID通常使用RFC 4122标准生成。UUID v7有效识别号
可排序部分是的是的
抗碰撞是的是的是的是的
数据库友好部分是的是的
隐私安全没有(MAC 地址)是的是的是的
标准机构IETF RFC 4122IETF RFC 4122IETF RFC 9562社区规范
典型长度36个字符36个字符36个字符26个字符
熵源MAC + 时钟随机的时间戳 + 随机时间戳 + 随机

何时使用每种类型

UUID v7 ——用于任何新系统中的数据库主键。它是 IETF 标准,正在增长的库支持(PostgreSQL 17原生支持,各大语言均有库支持),并且提供了无需协调的 B 树友好排序。

UUID v4 UUID v4(随机 UUID)是一种通过随机数生成的全球唯一标识符,通常用于数据库记录、文件系统和网络应用程序中。它由32位二进制数组合而成,并以“-”分隔为五个部分:8-4-4-12。 **注意:** 此版本的UUID通常使用RFC 4122标准生成。 ——继续用于任何需要随机性的安全敏感场景:会话令牌、密码重置令牌、API 密钥、OAuth 状态参数、日志中的相关 ID。这里的不可预测性是优点,而非缺陷。

有效识别号 ——如果你的团队已经使用它,特别是在 JavaScript 或 Go 项目中,使用它。更短的格式在 URL 和日志中确实更美观。如果你是全新项目,UUID v7 是更安全的长期选择,因为它有 IETF 支持。

UUID v1 ——不要。v1 在任何新代码中都没有合适的使用场景。

迁移考虑事项

如果你正在运行一个已有系统,其主键使用 UUID v4,你无需立即迁移——也不应随意进行迁移。外键关系、应用程序代码和可能的缓存值都引用这些 ID。迁移需要仔细规划,并且几乎必然需要维护窗口。

对于大多数团队,务实的方法是:在所有新表和服务中使用 UUID v7(或 ULID)。接受你的旧表仍使用 v4,并在性能影响可测量时通过定期索引重建来管理碎片化。不要让完美成为良好实践的敌人。

如果你是全新项目——新系统、新数据库、新表——那么没有必要为主键选择 v4。工具已经准备就绪。 生成一些 UUID v7 的样本 并看看你将面对什么。

总结

UUID v4 并不错误——只是经常被误用。它的随机性是安全属性,你应该在安全关键场景中保留它。对于数据库主键而言,这种随机性在系统扩展时会变成性能负担。

UUID v7 清晰地解决了这个问题:单调递增、全局唯一、标准化,并且已在你使用的数据库和 ORM 中得到支持。如果你今天正在编写新的数据库模式,请将 v7 作为默认选择。未来的你——以及你的 DBA——都会注意到这种差异。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?