ULID 是什么:可按时间排序的唯一 ID 实战指南
ULID 是一种 26 位、可按字典序排序的唯一标识符,把毫秒时间戳和随机位编进同一串字符。本文讲清它的结构、和 UUID 的区别,以及为什么适合做数据库主键。
ULID 是什么:可按时间排序的唯一 ID 实战指南
给数据库选主键的时候,UUIDv4 几乎是默认答案:全局唯一、客户端就能生成、不依赖数据库自增。但用久了会发现一个尴尬:两条相隔一秒插入的记录,它们的 ID 之间没有任何先后关系。想知道"哪几行最新",只能再建一个 created_at 列,再加索引,再 ORDER BY 它。
ULID 解决的正是这件事。它的全称是 Universally Unique Lexicographically Sortable Identifier,翻译过来是"全局唯一、可按字典序排序的标识符"。核心卖点就藏在名字里:可排序。
ULID 长什么样
先看一个真实例子。生成出来的 ULID 是这样一串:
01HQ3M9X7K8YVZ2N4P6R0TBEAS
26 个字符,没有连字符,全是大写字母和数字。把它直接放进 URL 也安全,不用做转义。对比一下 UUID 的 f47ac10b-58cc-4372-a567-0e02b2c3d479,带 4 个连字符、36 个字符,ULID 明显更短更清爽。
更关键的是,如果你连续生成几个 ULID,把它们当普通字符串从小到大排,排出来的顺序就是它们的生成顺序。这不是巧合,而是结构决定的。
结构:48 位时间戳 + 80 位随机
ULID 总共 128 位,和 UUID 一样大,但拆法不同。它用 Crockford Base32 编码,每个字符存 5 位信息。26 个字符这样分:
- 前 10 位字符:48 位毫秒时间戳。10 字符 × 5 位 = 50 位,装下 48 位时间绰绰有余,高位在前(大端序)。
- 后 16 位字符:80 位随机数。16 字符 × 5 位 = 80 位。
48 + 80 = 128 位,刚好和 UUID 对齐。有个细节挺有意思:首字符只用到 5 位里的 3 位,所以合法 ULID 的第一个字符不会超过 7。如果你看到一个以 8 或 9 开头的"ULID",它一定是假的。
Crockford Base32 还故意去掉了容易看错的字母 I、L、O、U,所以人眼读和手抄都不容易出岔子。我自己在排查线上问题时就吃过亏:用允许整个 A 到 Z 的正则去校验 ULID,结果把一个手写错的畸形 ID 当成合法的放过去了,查了半天才反应过来是字母表用错了。要校验,就得按那 32 个字符的精确集合来,不能图省事。
为什么字符串排序就等于按时间排序
道理很直接:时间戳放在最前面,而且是大端编码,高位在前。字符的字典序和时间戳的数值序是一致的,所以先生成的 01ARZ3NDEK... 永远排在后生成的 01ARZ3NF... 前面。后面那 16 位随机字符只在一种情况下起作用:两个 ID 落在同一毫秒里,这时靠随机尾巴区分先后。
如果你需要同一毫秒内也严格升序,可以用ULID 生成器的单调递增模式。开了之后,生成器在同一毫秒内复用时间戳、把随机部分加 1,保证 id[1] > id[0] 恒成立。事件日志、outbox 表这类每毫秒插很多行又要靠 ID 排序的场景,这个模式是刚需。
用作主键的好处
把 ULID 当主键,实际收益有这么几条:
第一,取最新记录不用额外的时间列。"给我最新 50 行"直接写 ORDER BY id DESC LIMIT 50,省掉一个 created_at 列和它的索引。
第二,索引更友好。ULID 大致按时间递增,B-tree 索引里新插入的值集中在右端,而不是像 UUIDv4 那样满树乱跳,页分裂和随机写更少。
第三,从 ID 反查时间。工单里贴了个记录 ID,你不查库就能知道它什么时候建的,把它粘进解码器读出精确到毫秒的时间即可。
不过有一点要拎清楚:ULID 是标识符,不是密钥。那 80 位随机让碰撞几乎不可能,但时间那一半完全可读,把 ULID 放进公开 URL 等于泄露了记录大致的创建时刻。会话 token、密码重置码这种必须整体不可猜的东西,该用密码生成器那类工具产出的真随机串,别拿 ULID 凑数。
什么时候不必换成 ULID
ULID 不是银弹。如果你的表本来就靠数据库自增主键、又没有分布式生成 ID 的需求,自增整数更小更快。如果你只是要个一次性的随机标识、根本不在乎顺序,UUIDv4 也够用。ULID 的价值集中在"既要全局唯一、又想按生成时间排序"这个交叉点上,踩中了就很省事,踩不中就是多余的复杂度。
选型前,先在生成器里实际生成几个看看格式,把一个真实 ULID 粘进解码器验证一下时间戳能不能正确还原,心里有底了再决定列类型。
Made by Toolora · Updated 2026-06-13