
ULID 究竟是什么
ULID(通用唯一字典序可排序标识符)是一个 128 位的标识符,以 26 个字符表示,设计上像 UUID 一样唯一,但可按创建时间排序。典型的 ULID 如下所示:
01ARZ3NDEKTSV4RRFFQ69G5FAV
128 位分为两部分:前 48 位是毫秒级 Unix 时间戳,后 80 位是随机数。由于时间戳在前且编码保持字节顺序,将 ULID 作为纯字符串排序即可按时间排序——这正是其关键所在。

26 字符格式
ULID 使用 Crockford 的 Base32 编码(数字 0-9 和字母 A-Z,排除 I、L、O、U 以避免歧义)。布局固定如下:
01ARZ3NDEK TSV4RRFFQ69G5FAV
|----------| |----------------|
时间戳 随机数
48 位 80 位
10 字符 16 字符
由此产生几个特点:
- ULID 不区分大小写且不含特殊字符,因此在 URL 中无需编码即可安全使用。
- 时间戳覆盖到公元 10889 年,溢出不是实际问题。
- 80 位随机数意味着同一毫秒内发生碰撞的概率极低。
在代码中生成 ULID
大多数语言都有小型库。在 JavaScript 中:
import { ulid } from 'ulid';
ulid(); // '01ARZ3NDEKTSV4RRFFQ69G5FAV'
// 你也可以传入显式时间戳(毫秒)
ulid(1469918176385); // '01ARYZ6S41TSV4RRFFQ69G5FAV'
在 Python 中:
from ulid import ULID
str(ULID()) # '01ARZ3NDEKTSV4RRFFQ69G5FAV'
解码时间戳也很直接,因为前 10 个字符就是 Base32 编码的毫秒数:
import { decodeTime } from 'ulid';
decodeTime('01ARZ3NDEKTSV4RRFFQ69G5FAV'); // 1469918176385

单调生成
在同一毫秒内,两个普通 ULID 的随机顺序是相对的。如果你每秒生成许多 ID 且需要严格递增,请使用单调工厂,它会递增随机部分而不是重新生成:
import { monotonicFactory } from 'ulid';
const next = monotonicFactory();
next(); // 01ARZ3NDEKTSV4RRFFQ69G5FAV
next(); // 01ARZ3NDEKTSV4RRFFQ69G5FAW ← +1,同一毫秒
next(); // 01ARZ3NDEKTSV4RRFFQ69G5FAX
当 ULID 用作主键且依赖它们反映插入顺序时,这一点很重要。
ULID 与 UUID 作为数据库键的对比
团队选择 ULID 的实际原因是索引性能。随机的 UUIDv4 键会将插入分散到 B-tree 索引中,导致页分裂和缓存局部性差。由于 ULID 按时间排序,新行会追加到索引的“右边缘”,类似于自增整数——但无需中央序列,也不会泄露行数。
| 属性 | UUIDv4 | ULID |
|---|---|---|
| 按时间排序 | 否 | 是 |
| 插入时的索引局部性 | 差(随机) | 好(顺序) |
| 文本长度 | 36 字符 | 26 字符 |
| 是否暴露创建时间 | 否 | 是(嵌入时间戳) |
| 无需编码即可用于 URL | 否(含连字符) | 是 |
一个值得注意的权衡:ULID 会向任何拥有它的人暴露其创建时间。如果面向公众的标识符不能透露记录创建时间,请改用随机 UUID。

何时使用 ULID
ULID 是以下场景的强默认选择:
- 高插入表的主键(订单、事件、消息)
- 需要客户端生成 ID 且无需协调的分布式系统
- 日志和事件标识符,按时间排序有助于调试
- 公共 ID,需要紧凑且 URL 安全的字符串,优于 UUID
当创建时间必须保密,或现有系统强制要求 UUID 格式时,应避免使用。
常见问题
ULID 有多长? 128 位,以 Crockford Base32 编码为 26 个字符——10 个字符的时间戳后跟 16 个字符的随机数。
ULID 保证唯一吗? 数学上不保证,但每毫秒 80 个随机位使得碰撞概率极低,实践中可视为唯一。使用单调生成还可保证同一毫秒内的顺序。
能从 ULID 中获取时间戳吗?
可以。前 10 个字符解码为毫秒级 Unix 时间戳,因此严格来说不需要单独的 created_at 列。
ULID 比 UUID 更好吗? 对于按时间排序、索引友好的键,是的。对于不能泄露创建时间的标识符,随机 UUID 更好。它们解决不同的问题。
使用上面的 ULID 生成器创建单个或批量 ULID,检查嵌入的时间戳,并直接复制到数据库或测试夹具中。