选择正确的唯一标识符 (ID) 是系统架构中最关键的决策之一。选择不当的 ID 可能会导致数据库性能瓶颈、安全漏洞或分布式系统同步问题。长期以来,UUID v4 一直是开发者的“默认”选择。然而,随着分布式系统和大规模数据库的发展,UUID v4 的局限性(如缺乏可排序性和 B-Tree 索引碎片化)变得越来越明显。
在本指南中,我们将深入探讨 UUID 的演变,研究 ULID 和 NanoID 等现代替代方案,并提供一个框架,帮助您根据具体用例选择最佳 ID。
1. UUID 的演变 (从 RFC 4122 到 RFC 9562)
通用唯一标识符 (UUID) 数十年来一直是行业标准。最近,IETF 通过 RFC 9562 更新了标准,引入了专门为现代数据库和分布式系统需求设计的新版本。
UUID v1:基于时间
UUID v1 结合了当前时间戳、时钟序列和生成器的 MAC 地址。
- 优点:保证在空间和时间上的唯一性(如果 MAC 地址唯一)。
- 缺点:隐私问题(暴露 MAC 地址),且如果在不同机器上生成,则不是单调递增的,这会损害 B-Tree 性能。
UUID v3 & v5:基于名称 (MD5/SHA-1)
这些是确定性的。如果您提供相同的命名空间和名称,您将得到相同的 UUID。
- UUID v3:使用 MD5。
- UUID v5:使用 SHA-1(优于 v3)。
- 用例:当您需要基于唯一输入生成可复现的 ID 时(例如,从 URL 生成 ID)。
UUID v4:随机
最常见的版本,由 122 位随机位组成。
- 优点:碰撞概率极低,不暴露敏感信息。
- 缺点:完全不可排序。当用作 B-Tree 索引(如 MySQL 的 InnoDB)的主键时,它会导致严重的“索引碎片”和“页拆分”,随着表的增长,性能会严重下降。
新一代:UUID v6, v7 和 v8
RFC 9562 引入了这些版本,旨在解决 v4 的排序问题,同时保持 UUID 格式。
UUID v7:新的金标准
UUID v7 是时间有序的。它使用 48 位 Unix 时间戳(毫秒精度),后跟随机位。
- 为什么它很棒:它是按字典顺序可排序的。当用作数据库主键时,新记录被追加到 B-Tree 的末尾,从而最大限度地减少页拆分并保持高性能。
- 建议:对于几乎所有新项目,UUID v7 应该取代 UUID v4 作为默认 ID。
UUID v6
基本上是重新排序的 UUID v1,使其可排序。仅当您对 UUID v1 结构有遗留依赖但需要排序功能时才使用它。
UUID v8
允许自定义实现,同时保持 UUID 结构。适用于需要在 128 位 ID 中嵌入特定元数据的专有系统。
2. 现代替代方案深度解析
虽然 UUID 是标准,但由于在可读性、长度或分布式生成方面的特定优势,几种替代方案也广受欢迎。
ULID (Universally Unique Lexicographically Sortable Identifier)
ULID 是 UUID v7 的强有力竞争者。
- 结构:48 位时间戳 + 80 位随机数。
- 编码:使用 Crockford 的 Base32,长度仅为 26 个字符(相比之下,标准 UUID 字符串为 36 个字符)。
- 优点:URL 安全、不区分大小写、可排序,且视觉上比 UUID 更短。
- 缺点:不是标准的 UUID 格式(某些旧数据库类型处理它的效率可能不如原生的 128 位 UUID/GUID 类型)。
NanoID:小巧快速的替代方案
NanoID 常用于前端和 Web 应用。
- 优点:比 UUID 小得多,字母表高度可定制,比 UUID v4 更快。
- 安全性:使用加密强度的随机生成器。
- 用例:非常适合短链接生成器、需要简短且不可猜测字符串的面向公众的记录 ID。
CUID2:下一代安全 ID
CUID2 旨在实现安全性和水平扩展友好性。
- 特性:极高的抗碰撞性、非连续性(防止枚举攻击),且可跨编程语言移植。
- 用例:当安全性和水平扩展比严格的时间排序更重要时。
Snowflake ID:分布式重量级方案
Snowflake ID 最初由 Twitter 开发,是 64 位整数。
- 结构:时间戳 + 工作机器 ID + 序列号。
- 优点:生成速度极快,适合标准 BIGINT(64 位),且时间有序。
- 缺点:需要集中或协调的“工作机器 ID”分配,以防止集群中的碰撞。
3. 对比表
| 特性 | UUID v4 | UUID v7 | ULID | NanoID | Snowflake | CUID2 |
|---|---|---|---|---|---|---|
| 长度 | 128 位 | 128 位 | 128 位 | 可变 | 64 位 | 可变 |
| 可排序性 | 否 | 是 (时间) | 是 (时间) | 否 | 是 (时间) | 否 |
| 格式 | 十六进制 | 十六进制 | Base32 | 字母数字 | 整数 | 字母数字 |
| 碰撞风险 | 微不足道 | 微不足道 | 极低 | 可配置 | 零 (若有协调) | 极低 |
| 可读性 | 差 | 差 | 好 | 极佳 | 好 | 好 |
| 数据库原生支持 | 是 | 是 | 否 (二进制/字符串) | 否 (字符串) | 是 (BIGINT) | 否 (字符串) |
4. 选择 ID 的最佳实践
用于数据库主键
- 如果您的数据库支持 128 位 UUID(PostgreSQL、现代 MySQL、SQL Server),请使用 UUID v7。它在唯一性和 B-Tree 性能之间提供了最佳平衡。
- 如果您正在构建大规模分布式系统并希望通过 64 位整数节省空间,请使用 Snowflake ID。
- 避免在大表中使用 UUID v4;随机插入会摧毁您的写入性能。
用于公开 URL
- 使用 NanoID 或 SQID。它们简短、URL 安全且美观。
- 短 UUID(Base58 或 Base62 编码的 UUID)也是一个很好的选择,既保持了底层的唯一性,又提供了更简洁的 URL。
用于分布式系统 (微服务)
- ULID 或 KSUID 是极好的选择,因为它们不需要中央协调器(与 Snowflake 不同),但仍然提供基于时间的排序,这对调试和日志记录非常有用。
5. 代码示例
在 Node.js 中生成 UUID v7
使用 uuid 包:
const { v7: uuidv7 } = require('uuid');
console.log(uuidv7()); // 例如 '018c3b7a-6b5d-7e8c-9a1b-2c3d4e5f6g7h'
在 Python 中生成 ULID
使用 python-ulid 库:
from ulid import ULID
ulid = ULID()
print(ulid) # 例如 '01H6P7XG6WJ9S5H8K4M6N7B2P1'
6. 常见陷阱 (FAQ)
问:我可以将现有的 UUID v4 转换为 UUID v7 吗? 答:不可以,位结构不同。但是,您可以开始为新记录使用 UUID v7。大多数数据库可以在同一个 UUID 列中存储两者。
问:NanoID 是否和 UUID 一样安全? 答:是的,前提是选择了正确的长度和字母表。21 个字符的 NanoID 与 UUID v4 具有相似的碰撞概率。
问:为什么不直接使用自增整数?
答:自增整数对于小型、单节点数据库非常有用。但是,它们会向竞争对手泄露您的数据量(例如 user/5000 告诉别人您有 5000 名用户),并且在分布式系统中难以管理。
7. 总结
在 2026 年,几乎没有理由在数据库主键上坚持使用 UUID v4。UUID v7 的兴起提供了一个标准化、时间有序的解决方案,在保持兼容性的同时解决了性能问题。对于特殊需求,ULID 提供了更好的可读性,而 Snowflake ID 在大规模分布式架构中提供了最高效率。
在确定 ID 方案之前,请评估您对可排序性、可读性和性能的需求。未来的您(以及您的数据库)会感谢现在的决定。