ULID 生成器

生成可排序的 26 位 ULID(Crockford base32)并可将内嵌时间戳解码回日期,非常适合作为数据库主键。

可排序的唯一标识。 ULID 为 26 位字符:前 10 位以毫秒编码创建时间,后 16 位为随机值。因此它可按字典序排序,也能安全用作主键。在浏览器内生成。

ULID 与 UUID 的取舍

ULID 是什么

ULID 是一个 128 位标识符,宽度与 UUID 相同,分为两部分:48 位毫秒时间戳,后接 80 位随机数据。它以 26 个字符的 Crockford base32 表示,而不是 UUID 那种带连字符的 36 字符十六进制,因此更短、大小写不敏感,并且剔除了最容易看错的字符——字母表中不含 I、L、O 和 U。

真正关键的差异不在编码,而在排序。由于时间戳占据高位,而 base32 保持字节序,把 ULID 当作普通字符串排序,结果就是按创建时间排序。同一毫秒内生成的两个 ID 会退化为比较随机部分,但跨毫秒的顺序是精确的。

可排序性对数据库为什么重要

UUIDv4 是均匀随机的,而这恰恰是聚簇索引最不需要的性质。每次插入都落在 B 树的随机位置,数据库因此不断分裂页面并触碰冷页。索引膨胀到超出必要的体积,写入吞吐也随表增长而劣化。这是 MySQL InnoDB 和 SQL Server 中 UUID 主键众所周知的问题。

按时间有序的标识符会追加在索引的右端附近,访问模式与自增整数一致。你既保留了客户端生成全局唯一键的好处——无需往返取 ID、服务之间无需协调、跨分片合并安全——又避开了索引碎片化。

什么时候该换别的方案

RFC 9562 标准化的 UUIDv7 用标准 UUID 的格式和布局实现了与 ULID 相同的效果。如果你的技术栈已经具备原生 UUID 列类型和配套工具,如今通常应优先选 UUIDv7,理由很简单:它能直接融入既有生态。而当你希望在 URL 或日志中使用更短、更友好的文本形式时,ULID 依然有吸引力。

无论选哪种,都要清楚时间戳对任何持有该 ID 的人都是可见的。创建时间会泄露,相邻 ID 会暴露先后顺序——凡是枚举关系或时间信息敏感的场景都不要使用。另外,每毫秒 80 位随机数意味着碰撞在实践中不必担心,但 ULID 并不是秘密,绝不能当作访问凭证使用。

开源说明:使用 crypto.getRandomValues 与自研 Crockford base32 编解码实现,不依赖第三方库。

常见问题

ULID 和 UUID 有什么区别?
两者都是 128 位,但 ULID 把 48 位毫秒时间戳放在最前面,因此按字符串排序就等于按创建时间排序。UUIDv4 完全随机,排序结果没有意义。
可排序对数据库为什么重要?
随机主键会把插入操作打散到 B 树各处并造成索引碎片。时间有序的主键总是追加在尾部附近,写入保持顺序,热页数量也小得多。
同一毫秒内生成的 ID 还能保持有序吗?
在单调模式下可以。工具不会重新抽取随机部分,而是对其递增,因此同一毫秒内生成的多个 ID 依然按顺序排列。
ULID 可以公开暴露吗?
随机部分无法猜测,但时间戳任何人都能读出来。如果记录的创建时间属于敏感信息,就不要用 ULID 作为它的公开标识符。
应该选 ULID 还是 UUIDv7?
两者解决的是同一个问题。UUIDv7 已在 RFC 9562 中标准化,能直接沿用现有的 UUID 字段和类库,新项目优先选它,除非你确实需要 26 字符的 Crockford 形式。
为什么是 26 个字符?
128 位用 Crockford Base32 编码正好需要 26 个符号。该字母表剔除了 I、L、O、U,既避免转录错误,也避免拼出意外的单词。