UUID / ULID / Token 标识符指南
UUID、ULID 与随机 Token 是开发者最常用的三类标识符。本页对比它们的格式、长度、是否可排序以及典型适用场景,帮助你在数据库主键、API Key 和会话 ID 之间做出合适选择。
实践注解
- v4 随机但无序,做主键会造成页分裂v4 的 122 位随机性带来索引随机插入,MySQL/Postgres 的 B-tree 会频繁分裂,写入吞吐下降、存储碎片上升。写入密集的表应改用 v7(时间前缀有序)或 ULID。
- UUID 不等于「不可猜测」v1 含 MAC 与时间戳,可被反推机器与生成时刻;v4 虽随机,但若用 Math.random() 生成就不具备密码学强度。凡是当令牌或密钥使用的标识,必须用 CSPRNG(Web Crypto 的 randomUUID 或 getRandomValues)。
- 存储形态影响体积与查询以 36 字符文本存 1000 万行约 360 MB,改存 16 字节 BINARY(16) 可降到约 160 MB,且比较更快。代价是查询与日志可读性下降,通常的做法是内部用二进制、对外展示用带连字符的字符串。
数据来源
- RFC 4122 (UUID)
- RFC 9562 (UUID v6 / v7 / v8)
- ULID specification (ulid/spec)
上述编号、名称与定义均来自公开规范,本站不主张对规范数据本身的独创性;本页的编排方式、中文释义与实践注解由本站撰写。
原理解析
标识符要解决两个看似矛盾的目标:全局唯一 与 可排序/可预测。早期 UUID v4 纯随机,保证唯一但不泄露时间、不可排序;当数据库需要按插入顺序扫描(避免 B+ 树页分裂)时,就出现了带时间前缀的方案——UUID v7(前 48 位 Unix 毫秒时间戳)和 ULID(前 48 位 Crockford Base32 时间戳)。
Token 与 UUID 定位不同:UUID 以「唯一」为设计目标,适合做主键/对象 ID;API Key、Session、激活码这类「秘密」应优先用加密安全随机 Token,并在长度、字符集、前缀(如 sk_live_)上做权限区分。Base58 / Base62 在需要手动输入时去掉易混淆字符(0/O、I/l)更安全。
什么时候该查这张表
这张表在你要给系统选标识符方案时有用。它把 UUID 各版本、ULID、KSUID、雪花 ID、ObjectId、Token、API Key 放在同一维度对比:长度、熵、是否可排序、典型场景。选型时不必在多个文档间跳。
不该查的情况:你在设计分布式一致性与可用性的取舍。ID 能不能趋势递增、会不会泄露业务量、跨机房时钟漂移怎么处理,这些是架构决策不是查表能解决的,表只给静态属性对比。
判断标准:先问「需不需要按时间排序」。需要 ⇒ 选时间有序方案(ULID/KSUID/雪花),随机 UUIDv4 虽唯一但索引会随机插入、影响 B+ 树局部性。不需要 ⇒ UUIDv4 最省心,跨语言原生支持且无协调成本。
特别注意熵不等于强度:表中「长度/熵」列给出的是位宽,不是抗猜测能力。128 位随机值的强度取决于生成方式是否用密码学安全随机源,用 Math.random 拼出来的 UUID 熵再高也可预测。
边界与常见陷阱
UUIDv4 用 Math.random 生成会显著削弱唯一性与安全性
Math.random 产生的是伪随机序列,周期约 2^53 且状态可被观测者推断。批量生成时极易碰撞(在数百万量级上就能观察到重复),用它拼出的 UUID 在安全上也不可依赖。正确做法是用密码学安全源:浏览器用 crypto.randomUUID() 或 crypto.getRandomValues(new Uint8Array(16)),Node 用 crypto.randomUUID() 或 crypto.randomBytes(16)。
UUIDv1 泄露 MAC 地址与时间戳,v4 与 v7 才是当前选择
v1 由「时间戳 + 节点标识」拼成,节点标识常取 MAC 地址,因此任何人拿到你的 v1 ID 都能推算出生成时间与机器信息。v6/v7 是 RFC 9562 引入的现代替代:v7 把毫秒时间戳放在高位,兼顾唯一性与趋势递增,且不暴露 MAC。选型口诀:需要排序选 v7(ULID 同理),不需要就 v4,任何情况下别再用 v1 做对外 ID。
把标识符直接暴露给客户端会泄露业务规模
自增 ID 或时间有序 ID 隐含了「总量 = 最大值」的规律,竞品爬一遍你的接口就能推出订单量、用户数与增长曲线。合规上也越来越紧:多数司法辖区把这类可识别标识视作个人信息(受 GDPR 类法规约束)。做法是对外用不透明 ID、对内保留有序主键,或直接用 UUIDv4/ULID 让 ID 与业务量脱钩。
时间有序方案的跨机房时钟漂移会破坏顺序
ULID/KSUID 依赖单机时钟,机房之间 NTP 偏差会导致时间戳倒退,进而让「按 ID 排序 ≠ 按时间排序」,甚至主键冲突。雪花算法(snowflake)用机器位区分节点,逻辑上能容忍小范围漂移,但漂移超过回拨容忍窗口仍会撞号。缓解手段:启动时校时并监控 offset、必要时拒绝分配、使用单调递增计数器兜底。
与相邻内容的分工
要生成随机标识符或密码,用站内 UUID 与随机工具;本页给的是选型对比,不是生成器。
要处理标识符在 URL、文件名、数据库主键中的编码问题,JSON 编码与字符集相关页更贴近;本页只谈方案选择。
涉及 OAuth、JWT、API Key 的协议细节与生命周期管理,属认证体系范畴,需要系统设计而非查表。
若目的是排查「ID 冲突」或「ID 重复」这类线上故障,本表属性对比不足以定位,需要看具体生成代码与日志。
实战片段
要点:① UUID 必须用 crypto.randomUUID 或 getRandomValues 生成,Math.random 会让 128 位熵名存实亡,批量下还会碰撞;② 版本选择口诀——需排序选 v7 或 ULID,否则 v4,对外 ID 永远别用 v1(泄露 MAC 与生成时间);③ 时间有序方案依赖时钟,跨机房 NTP 漂移会破坏「ID 序 = 时间序」,需监控 offset;④ 对外 ID 要不透明,否则竞品爬接口即可推出业务量,且可能触及个人信息合规红线;⑤ 自制时序 UUID 容易写错版本位与变体位,生产请用成熟库。
// ① 生成:必须用密码学安全源
const id = crypto.randomUUID() // ✅ 浏览器/Node 18+
// ❌ Math.random 拼出来的 UUID:可预测、批量下会碰撞
const bad = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
c => (Math.random() * 16 | (c === 'y' ? 4 : 0)).toString(16))
// ② 自己拼 v4(需要控制格式时)
function uuidV4() {
const b = crypto.getRandomValues(new Uint8Array(16))
b[6] = (b[6] & 0x0f) | 0x40 // version 4
b[8] = (b[8] & 0x3f) | 0x80 // variant 10
const h = [...b].map(x => x.toString(16).padStart(2, '0')).join('')
return [h.slice(0, 8), h.slice(8, 12), h.slice(12, 16), h.slice(16, 20), h.slice(20)].join('-')
}
// ③ 时间有序:v7 / ULID(需按时间排序时选这类)
const v7 = crypto.randomUUID().replace(/^(.{8})-(.{4})-(.{4})/, (m, a, b, c) => {
const ts = BigInt(Date.now()).toString(16).padStart(12, '0')
return ts.slice(0, 8) + '-' + ts.slice(8, 12) + '-' + '7' + c.charAt(0) + '-' + m.slice(19)
})
// 说明:生产环境请直接用 uuid@13+ 或 ulid 库,别手搓时序
// ④ 熵 ≠ 强度:检查生成源
console.log(typeof crypto.randomUUID()) // 'function' ⇒ 安全源
console.log(typeof Math.random) // 'function' ⇒ 伪随机,不要用于 ID
// ⑤ 对外不透明、内部有序:用映射表把外部 ID 换成主键
// const publicId = nanoid() // 外部可见
// const row = await db.byPublicId(publicId) // 内部仍用自增主键| 标识符 | 格式 | 长度 / 熵 | 可排序 | 最佳场景 |
|---|---|---|---|---|
| UUID v4 | xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx | 122 位随机 + 6 位版本/变体 | 否(完全随机) | 数据库主键、分布式 ID、隐私场景 |
| UUID v7 | 时间戳(48bit) + 随机(74bit) | 128 位 | 是(按时间单调递增) | 需要可排序主键的新系统 |
| ULID | TTTTTTTTTTRRRRRRRRRRRRRRRR(Crockford Base32) | 48 位时间戳 + 80 位随机 | 是(按生成时间字典序) | URL 安全、可排序、人类可读标识符 |
| 随机 Token | 十六进制 / Base64 / Base62 字符串 | 按需(常见 128–256 位) | 否 | API Key、Session、激活码、邀请码 |
常见问题
UUID 会重复吗?
UUID v4 的冲突概率极低:理论上需要生成约 10^18 个 122 位随机 UUID 才可能出现一次碰撞,远超常规业务规模。UUID v7 和 ULID additionally 引入时间前缀,同一毫秒内生成的 ID 仍有 80 位随机空间,冲突风险同样可忽略。
UUID v4 和 ULID 该怎么选?
需要可排序、URL 更短、可读性更好 → 选 ULID。需要最广泛兼容、隐私性最强(完全看不出生成时间)→ 选 UUID v4。新项目若数据库主键需要按时间排序,可直接考虑 UUID v7。
Token 应该用什么字符集?
十六进制最简单、最易读;Base64 最短但含 +/= 可能污染 URL;Base62 去掉特殊符号,兼顾长度与 URL 安全;Base58 进一步排除易混淆字符(0/O、I/l),适合手动输入场景。
API Key 用 UUID 还是随机 Token?
API Key 建议用加密安全随机 Token(长度至少 128 位),不要直接用 UUID。UUID 主要设计为唯一标识,虽然熵足够,但专用 Token 在长度、字符集、前缀标识(如 sk_live_xxx)上更灵活,也更容易做权限前缀区分。