ULID生成器

Generate lexicographically sortable unique IDs

数量

点击"生成"以创建 ULID

什么是 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 标准化的替代方案,具有类似的时间排序属性。