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 位,分为两个组件:
| 时间戳 | 随机 |
|---|---|
| 01ARZ3NDEK | TSVE4RRFFQ69G5FAV |
| 48 位——Unix 毫秒时间戳。以 1 毫秒精度编码生成时间。覆盖至公元 10889 年。 | 80 位——加密安全随机数据。每个 ULID 新鲜生成(除非使用单调模式)。 |
编码为 26 个 Crockford Base32 字符:前 10 个字符表示时间戳,后 16 个字符表示随机组件。
Crockford Base32 字母表
ULID 使用 Crockford 的 Base32 编码,它使用 36 个字母数字字符中的 32 个。字母表为:0123456789ABCDEFGHJKMNPQRSTVWXYZ
四个字符被故意排除:
I和L被排除——容易与数字1混淆O被排除——容易与数字0混淆U被排除——避免生成的 ID 中出现意外的不雅词语- 编码是
不区分大小写的——01ARZ3NDEKTSV4RRFFQ69G5FAV和01arz3ndektsv4rrffq69g5fav是同一个 ULID
Crockford Base32 比十六进制更高效(32 个符号 vs 16 个),比 Base64 更易于人类阅读(无 + / = 字符,不区分大小写)。
ULID vs UUID
ULID 和 UUID 都表示 128 位标识符,但在编码和设计目标上有显著差异:
| 属性 | ULID | UUID |
|---|---|---|
| 格式 | 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 安全标识符,但它们有不同的设计目标:
| 属性 | ULID | NanoID |
|---|---|---|
| 时间戳 | 是——48 位 Unix 毫秒 | 否 |
| 可排序 | 是 | 否 |
| 默认长度 | 26 个字符 | 21 个字符 |
| 熵 | 80 位(随机组件) | 约 126 位 |
| 字母表 | Crockford Base32(32 个字符) | URL 安全 Base64(64 个字符) |
| 可自定义长度 | 否 | 是(任意长度) |
| 用例 | 时间排序 ID、数据库主键 | 随机令牌、短 URL、API 密钥 |
需要时间排序时选择 ULID。需要短随机字符串中最大熵时选择 nanoid。
在数据库中使用 ULID
ULID 可以根据你的需求以多种方式存储在数据库中:
代码示例
ULID 生成需要 ulid 库(适用于 JavaScript、Python、Go、Rust 等):
// 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)# 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-- 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;// 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)
}