UUID 和 ULID 是分布式系统以及数据库中用于发放“唯一 ID”的机制。它们看似形式相近,但在是否标准化、存储效率、人类可读性、信息暴露风险等方面存在差异。以下以最具代表性的三种方式——UUID v4UUID v7ULID——为中心整理特征与选型准则,最后补充UUID v1 / v6 的要点。

需要在浏览器内完成 ID 生成与时刻信息还原的工具在此:


UUID v4:完全随机型

UUID v4 在 128bit 中有 122bit 用于随机数,以极低的碰撞概率为强项。相应地,它无法按时间顺序排列,在 B-Tree 类索引中会频繁触发页分裂,插入局部性容易恶化。适用于会话 ID 或一次性密钥等“完全随机即可”的场景。


UUID v7:可按时间排序的正统进化

UUID v7 于 2024 年写入 RFC 9562前 48bit 持有 UNIX 纪元毫秒时间戳,其余部分填充随机数。字典序(字符串比较)即生成顺序(时序),因此即便用作主键,索引局部性也良好。此外可直接存入现有的 uuid 类型或 BINARY(16),也是优势所在。

  • 随机数位长度:除去内部的 version/variant 若干比特,v7 大约有 74bit 左右随机数。具体实现可能通过单调生成(避免同一毫秒内碰撞)微调部分随机位,但统计上的随机性足够。
  • 信息暴露风险:可从 ID 中推算生成时刻(毫秒精度)。若发号量巨大且随机实现薄弱,邻近 ID 被推测的可能性相对提高。内部系统通常可以接受,但公开端点若不希望暴露准连号,则需注意。

ULID:更易读的人性化 ID

ULID 于 2016 年提出,属于事实标准,使用 Crockford Base32 的 26 个字符 表示。它去掉了容易混淆的字符(O/I/L/1),便于嵌入 URL 或复制。同 UUID v7 一样,前 48bit 带有毫秒级时间戳,因此字符串字典序即时序

  • 排序保证:ULID Base32 字符串之间的字典序比较始终等价于时间顺序(单调生成的规格也已广泛普及)。
  • 存储形态:TEXT(26) 便于直接读取;若要以二进制存于数据库,则需进行 Base32↔16B 转换。虽然可作为 128bit 宽的二进制处理,但与 RDB 的 UUID 专用类型往往不兼容
  • 信息暴露风险:与 v7 相同,会暴露毫秒级时间。用作公开 API 资源 ID 且不希望显露发号顺序时需谨慎取舍。

三种方式的对比表(实务视角)

项目 UUID v4 UUID v7 ULID
标准化 RFC 4122 RFC 9562(2024) 非标准(事实标准)
比特构成 128bit 中 122bit 随机数 48bit=UNIX ms + 其余随机数(可单调生成) 48bit=UNIX ms + 80bit 随机数
随机熵 ≈122bit ≈74bit(扣除 version/variant 的估算) 80bit
字符串表示 36 字符(十六进制+连字符) 36 字符(十六进制+连字符) 26 字符(Crockford Base32)
自然排序(文本) ×(随机) ○(字典序=时序) ○(字典序=时序)
自然排序(字节) × ○(BINARY(16) 的 memcmp 升序) ○(此前缀 6B 时间以大端存放时 memcmp 升序
数据库存储特性 uuid 型 / BINARY(16) 最优 uuid 型 / BINARY(16) 最优 TEXT(26) 具可读性,BINARY(16) 需转换 / 多半不兼容 UUID 类型
索引局部性 差(随机插入导致分裂) 好(末尾追加/需留意热点) 好(末尾追加/需留意热点)
等值查询成本 低(16B 比较) 低(16B 比较) TEXT 略高(受 collation 影响)/BINARY 较低
编码负担 十六进制↔16B(轻) 十六进制↔16B(轻) Base32↔16B(稍重)
可读性/URL 适性
信息暴露(生成时刻) 有(毫秒) 有(毫秒)
主要用途 会话 ID、一次性密钥 数据库主键、事件 ID、日志时序 URL・公开 ID、日志可视化

补充(v7/ULID 的热点问题):若针对单一分片或单一主节点集中末尾插入,B-Tree 末端可能过热。可考虑 上位比特轻度扰动(“prefix shuffle”) 或配合 分片键


数据库实现备忘(PostgreSQL / MySQL / SQLite)

  • PostgreSQL
    • v4/v7:使用 uuid 类型最佳。v7 以字符串写入 uuid 可直接获得排序特性。
    • ULID:使用 char(26) / text,或转换为 bytea(16) 存放。
  • MySQL/InnoDB
    • BINARY(16) 对 v4/v7 来说高速又省空间。v7 按 大端布局 存储后 memcmp 即为时序。ULID 可用 CHAR(26)BINARY(16)+转换。
  • SQLite
    • 无专用 UUID 类型。可用 BLOB(16)TEXT。建立索引的 BLOB 比较效率相对较好。

安全性视角的补强

  • 抗推测性:v7/ULID 的时间位相同,若在同一毫秒内大量发号且随机实现薄弱,则邻近 ID 被推测的风险相对上升。务必使用操作系统提供的密码学安全 PRNG,并尽量降低单调增量的偏斜
  • 元数据泄漏:可从 ID 中推测生成时间与大致发号量。公开 API 建议内部 ID 与外部公开 ID 分离的设计。

总结(选型指引)

  • UUID v4:完全随机。无排序能力。理论上仍存在极小的碰撞风险。适用于会话 ID 或 CSRF Token 等。
  • UUID v7:标准化的有序 UUID。最适合内部系统与数据库主键。通过单调生成,可在实践中避免碰撞。
  • ULID:相对更“人性化”,便于读取。如果外部曝光或日志可视化重要,它是有力候选;但作为内部主键通常不如 v7。通过单调生成,可在实践中避免碰撞。

补充:UUID v1 / v6(简述)

  • UUID v1(基于时间+MAC) 包含 60bit 时间戳(100ns 精度)+节点标识(常为 MAC 地址)。具备较强的时序性,但由于含有 MAC 信息,存在泄露主机信息的担忧,并且在时钟回拨时需注意实现。现代多以隐私顾虑为由不再推荐。

  • UUID v6(v1 的重新排序版) 将 v1 的时间戳重新排成大端序以强化字典序排序。历史上曾被视为“更易排序的 v1”,但标准化最终收敛到 v7。新设计通常直接选择 v7 而非 v6


附录:实现检查清单

  • 随机数必须使用密码学级 PRNG(操作系统提供的安全源)。
  • v7/ULID 的单调生成既要避免同毫秒碰撞,又要控制随机数偏斜
  • 数据库优先使用 uuid 型 / BINARY(16),以避免字符串列的 collation 带来的性能下降。
  • 明确定义外部公开 ID 的策略(是否允许暴露顺序)。必要时拆分内部 ID 与外部 ID

结论是,目前技术上内部主键首选 UUID v7,若重视可读性或存在技术制约,则ULID 也是备选。只有在禁止暴露时间戳等特殊条件下,才会回退到 v4 或其他方式。