跳到主要内容

ULID 解码:从 26 位字符里读出生成时间

ULID 前 10 位藏着 48 位毫秒时间戳,后 16 位是随机。本文讲清 ULID 解码原理、和 UUID 的区别,以及怎样只凭一个 ID 查出它是什么时候生成的。

发布于 作者 李雷
#ULID #分布式ID #时间戳 #后端

ULID 解码:从 26 位字符里读出生成时间

我第一次盯着一串 ULID 发呆,是在排查一个支付回调乱序的工单。日志里每行都是 01ARYZ6S41TSV4RRFFQ69G5FAV 这样的 26 位字符,没有 created_at,也没法连库。后来才反应过来:时间就写在 ID 里,根本不用查。把它粘进 ULID 解码器,前 10 个字符立刻还原成一个精确到毫秒的时间。这篇文章就把这件事讲透。

ULID 是什么,凭什么能解码

ULID 全称 Universally Unique Lexicographically Sortable Identifier,一共 128 位,和 UUID 一样大。但它的编码方式不同:用 Crockford Base32 写成 26 个字符,按高位在前分成两段,前 10 位是时间,后 16 位是随机。

正因为时间被固定放在前导字符里,ULID 才是可解码的。它不像随机 ID 那样里面什么都没有,而是把生成时刻明明白白地编进了字符串。

前 10 个字符:48 位毫秒时间戳

这是最关键的一点:ULID 前 10 个字符,是一个用 Crockford Base32 编码的 48 位毫秒时间戳。

拆开看,Crockford Base32 每个字符存 5 位,10 个字符就是 50 位。其中高 48 位是距 Unix 纪元的毫秒数,首字符只用到 5 位里的 3 位。这也解释了一个细节:合法的 ULID 首位永远不会超过 7,因为再大就会让 48 位时间溢出。一个正确的解码器会直接拒绝首位是 8 或 9 的输入。

解码就是这段时间的逆运算:取前 10 个字符,按大端读成一个 Base32 数,得到毫秒数,再格式化成 ISO 8601、UTC 和本地时间三种写法。

一个真实的输入输出例子

拿规范里那个经典的 ULID 来跑一遍:

输入 01ARYZ6S41TSV4RRFFQ69G5FAV

  • 前 10 位 01ARYZ6S41 解出时间戳 1469918176385 毫秒
  • 格式化为 2016-07-30T22:36:16.385Z
  • 后 16 位 TSV4RRFFQ69G5FAV 是那 80 位随机部分

你看,不用查任何数据库,这个 ID 自己就告诉你它生成于 2016 年 7 月 30 日。48 位时间加 80 位随机,正好 128 位。

和 UUID 到底差在哪

很多人会问,既然都是 128 位,为什么不直接用 UUID。差别在于可解码和可排序。

UUIDv4 是全随机的,里面没有时间,也没有顺序,你拿到一个 UUID 没法知道它什么时候生成、和另一个谁先谁后。ULID 不一样:时间戳在前导字符里,所以两个 ULID 直接按字符串字典序排序,就等于按生成时间排序。

写法上也更省事。ULID 是 26 位、不带连字符,UUID 是 36 位、带四个连字符。需要放进 URL 时,ULID 不需要转义就是安全的。如果你手上是另一种 ID,可以用 UUID 生成器 对照着感受两者的区别。

在分布式系统里的实际用处

ULID 的可排序特性,在分布式场景里很值钱。多个节点各自生成 ID 不用协调,但只要时钟大致同步,生成出来的 ID 在全局上就大致有序。写进数据库主键时,新记录总是追加在 B 树的尾部,不像随机 UUID 那样到处插入、把索引页搅乱。

排查问题时也直接。一份每行一个 ULID 的日志,整块粘进解码器批量跑,每行都解成时间戳,时间线就出来了。坏行或被截断的行会被标为非法,而不是悄悄给出一个荒唐日期,你在读顺序的同时也能挑出损坏的条目。

解码时要注意的坑

我自己踩过的、也最常见的,是用整个 A-Z 字母表去校验 ULID。Crockford Base32 不含 I、L、O、U,正是为了避免人眼把 I 和 1、O 和 0 混淆。放行所有字母的正则,会把畸形 ID 当合法,然后悄悄解错。要按那 32 个符号的精确集合去校验。

另一个坑是从错的一头读时间。时间永远是前 10 个字符,后 16 位是纯随机,里面没有时间。从尾部切,或把整个 26 位当时间,得到的都是无意义的日期。

最后提醒一句安全:既然前 10 位会暴露精确到毫秒的创建时刻,放进公开 URL 的 ULID 就等于泄露了那条记录大致的生成时间。涉及敏感时间时,别把它直接挂在分享链接上。

小结

ULID 解码的核心只有一句话:前 10 个字符是 Crockford Base32 编码的毫秒时间戳,解出来就是生成时间。它比 UUID 多了可解码和可排序两样能力,在分布式 ID 和日志排查里都好用。想直接试一下,把你的 ID 粘进 ULID 解码器 即可,全程在浏览器本地完成。


Made by Toolora · Updated 2026-06-13