ToolDeck

ULID Generator

Generate lexicographically sortable unique IDs

Count

Click Generate to create ULIDs

什么是 ULID?

ULID(通用唯一字典排序标识符)是一种 128 位标识符格式,设计为既唯一又自然可排序。与完全随机的 UUID v4 不同,ULID 在其高位嵌入了毫秒精度的 Unix 时间戳——确保后来生成的 ID 排在先前生成的 ID 之后。

ULID 使用 Crockford 的 Base32 字母表编码,产生紧凑的 26 字符字符串,URL 安全、不区分大小写,且不含视觉上模糊的字符。

ULID 规范由 Alizain Feerasta 创建,在分布式系统、事件溯源和数据库主键中广泛使用,其中唯一性和时间排序都很重要。ULID 不是 IETF 标准——它是 ulid.github.io 上的社区规范。

ULID 结构

ULID 宽 128 位,分为两个组件:

时间戳随机
01ARZ3NDEKTSVE4RRFFQ69G5FAV
48 位——Unix 毫秒时间戳。以 1 毫秒精度编码生成时间。覆盖至公元 10889 年。80 位——加密安全随机数据。每个 ULID 新鲜生成(除非使用单调模式)。

编码为 26 个 Crockford Base32 字符:前 10 个字符表示时间戳,后 16 个字符表示随机组件。

Crockford Base32 字母表

ULID 使用 Crockford 的 Base32 编码,它使用 36 个字母数字字符中的 32 个。字母表为:0123456789ABCDEFGHJKMNPQRSTVWXYZ

为什么是这 32 个字符?
0123456789ABCDEFGHJKMNPQRSTVWXYZ

四个字符被故意排除:

  • IL 被排除——容易与数字 1 混淆
  • O 被排除——容易与数字 0 混淆
  • U 被排除——避免生成的 ID 中出现意外的不雅词语
  • 编码是不区分大小写的——01ARZ3NDEKTSV4RRFFQ69G5FAV01arz3ndektsv4rrffq69g5fav 是同一个 ULID

Crockford Base32 比十六进制更高效(32 个符号 vs 16 个),比 Base64 更易于人类阅读(无 + / = 字符,不区分大小写)。

ULID vs UUID

ULID 和 UUID 都表示 128 位标识符,但在编码和设计目标上有显著差异:

属性ULIDUUID
格式Crockford Base32连字符十六进制
长度26 个字符36 个字符
时间戳48 位 Unix 毫秒无(v4)或 48 位毫秒(v7)
可排序是——字典序否(v4)/ 是(v7)
URL 安全是(仅字母数字)是(含连字符)
依赖项需要库原生(crypto.randomUUID)
数据库支持存储为 CHAR(26) 或 BINARY(16)原生 UUID 列类型
规范社区规范(ulid.github.io)RFC 4122 / RFC 9562

如果你已经在 UUID 生态系统中(PostgreSQL uuid 列、REST API、具有 UUID 支持的 ORM),UUID v7 通常比 ULID 更合适。如果你更喜欢更友好的人类编码并控制整个技术栈,ULID 是一个很好的选择。

ULID vs nanoid

ULID 和 nanoid 都产生短小的 URL 安全标识符,但它们有不同的设计目标:

属性ULIDNanoID
时间戳是——48 位 Unix 毫秒
可排序
默认长度26 个字符21 个字符
80 位(随机组件)约 126 位
字母表Crockford Base32(32 个字符)URL 安全 Base64(64 个字符)
可自定义长度是(任意长度)
用例时间排序 ID、数据库主键随机令牌、短 URL、API 密钥

需要时间排序时选择 ULID。需要短随机字符串中最大熵时选择 nanoid。

在数据库中使用 ULID

ULID 可以根据你的需求以多种方式存储在数据库中:

存储为 CHAR(26)
最简单的方法。保留字典排序顺序。比二进制存储稍大,但人类可读且易于调试。
存储为 BINARY(16)
将 ULID 解码为其 16 字节二进制表示以进行紧凑存储。需要在应用程序代码中进行编码/解码。保留排序顺序。
PostgreSQL
存储为 CHAR(26) 或使用 bytea 类型进行二进制存储。没有原生 ULID 类型,但 CHAR(26) 上的字典排序可正常工作。
MySQL / MariaDB
使用 CHAR(26) CHARACTER SET ascii 进行高效存储。BINARY(16) 中的二进制存储也可行并节省空间。
SQLite
存储为 TEXT。SQLite 的默认文本比较由于 ULID 的字典设计可正确排序 ULID。
MongoDB
存储为字符串字段。ULID 的字典可排序性与 MongoDB 的字符串比较自然配合。

代码示例

ULID 生成需要 ulid 库(适用于 JavaScript、Python、Go、Rust 等):

JavaScript (ulid npm)
// Using the 'ulid' npm package
import { ulid } from 'ulid'

const id = ulid()          // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
const id2 = ulid()         // "01ARZ3NDEKXXXXXXXXXXXX..." (same ms, incremented random)

// With a custom timestamp
const id3 = ulid(1469918176385) // deterministic time portion

// Extract the timestamp back out
import { decodeTime } from 'ulid'
decodeTime(id)  // → 1469918176385 (Unix ms)
Python (python-ulid)
# Using python-ulid
from ulid import ULID

uid = ULID()
str(uid)                    # "01ARZ3NDEKTSV4RRFFQ69G5FAV"
uid.timestamp               # 1469918176.385
uid.datetime                # datetime(2016, 7, 30, 23, 36, 16, 385000, tzinfo=timezone.utc)

# Parse an existing ULID string
parsed = ULID.from_str("01ARZ3NDEKTSV4RRFFQ69G5FAV")
parsed.timestamp            # 1469918176.385
PostgreSQL
-- Store ULIDs as TEXT or CHAR(26)
CREATE TABLE events (
  id   CHAR(26) PRIMARY KEY DEFAULT gen_ulid(),  -- if using a PG extension
  name TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Or pass the ULID from your application layer
INSERT INTO events (id, name, created_at)
VALUES ('01ARZ3NDEKTSV4RRFFQ69G5FAV', 'user.signup', NOW());

-- Range queries are efficient because ULIDs sort chronologically
SELECT * FROM events
WHERE id > '01ARZ3NDEKTSV4RRFFQ69G5FAV'
ORDER BY id
LIMIT 20;
JavaScript (pure — no dependencies)
// Pure browser / Deno / Node.js implementation (no dependencies)
const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'

function encodeTime(ms) {
  let result = '', n = BigInt(ms)
  for (let i = 9; i >= 0; i--) {
    result = CROCKFORD[Number(n & 31n)] + result
    n >>= 5n
  }
  return result
}

function encodeRandom(bytes) {
  let n = 0n
  for (const b of bytes) n = (n << 8n) | BigInt(b)
  let result = ''
  for (let i = 15; i >= 0; i--) {
    result = CROCKFORD[Number(n & 31n)] + result
    n >>= 5n
  }
  return result
}

function generateULID() {
  const randomBytes = new Uint8Array(10)
  crypto.getRandomValues(randomBytes)
  return encodeTime(Date.now()) + encodeRandom(randomBytes)
}

常见问题

ULID 是全局唯一的吗?
是的——概率非常高。80 位随机组件提供每毫秒每个生成器 2^80 ≈ 1.2 × 10^24 个可能值。在任何实际系统中,不同生成器在同一毫秒内碰撞的概率可忽略不计。对于严格的唯一性保证,请使用单调模式,该模式会为同一毫秒内生成的 ID 递增随机组件。
ULID 与 UUID 相同吗?
不——ULID 和 UUID 是 128 位值的不同编码。ULID 不能直接替代验证 UUID 格式(连字符十六进制)的系统中的 UUID。但是,如果需要,你可以在二进制 ULID 和 UUID 表示之间进行转换。
ULID 单调模式如何工作?
在标准模式下,80 位随机组件对每个 ULID 新鲜生成。在单调模式下,当在同一毫秒内生成多个 ULID 时,第二个及后续 ID 的随机组件是前一个随机值加一。这确保了在单个生成器上毫秒内的严格单调排序。
我可以解码 ULID 来获取生成时间戳吗?
可以。前 10 个 Crockford Base32 字符编码了 48 位 Unix 毫秒时间戳。使用 Crockford Base32 字母表解码这些字符,将结果解释为以毫秒为单位的 Unix 时间戳,并转换为日期。此工具正是这样做的。
ULID 是官方标准吗?
不是。ULID 是在 ulid.github.io 维护的社区规范。它不是像 UUID 那样的 IETF 标准。这意味着没有官方 RFC 可以引用,实现可能略有不同。UUID v7(RFC 9562,2024 年)是 IETF 标准化的替代方案,具有类似的时间排序属性。