为应用程序选择正确的唯一标识符格式是一个影响数据库性能、可扩展性和开发者体验的决策。随着 UUID v7 成为正式标准(RFC 9562,2024 年发布),ID 生成的格局已经发生了重大变化。本指南比较了四种最流行的选项。
快速对比表
| 特性 | UUID v4 | UUID v7 | ULID | NanoID |
|---|---|---|---|---|
| 大小(字节) | 16 | 16 | 16 | Configurable (default 21 chars) |
| 字符串长度 | 36 chars | 36 chars | 26 chars | 21 chars (default) |
| 时间有序 | No | Yes (ms precision) | Yes (ms precision) | No |
| 标准 | RFC 9562 | RFC 9562 | Community spec | Community spec |
| 数据库索引友好 | Poor | Excellent | Excellent | Poor |
| 抗碰撞性 | 122 random bits | 74 random bits + timestamp | 80 random bits + timestamp | Configurable |
| URL 安全 | Yes (with encoding) | Yes (with encoding) | Yes (native) | Yes (native) |
| 语言支持 | Universal | Growing rapidly | Good | Excellent (JS/TS) |
UUID v4:成熟的标准
UUID v4 十多年来一直是默认选择。它使用 122 位随机数生成 128 位标识符,产生熟悉的 xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx 格式。
优势:
- 在所有编程语言中都有通用的库支持
- 无需协调——随时随地生成
- 简单且易于理解
劣势:
- 随机分布导致数据库中的 B-tree 索引碎片化
- 没有时间信息——无法按创建顺序排序
- 在大规模(数百万行)情况下,插入性能显著下降
// JavaScript
crypto.randomUUID();
// → "f47ac10b-58cc-4372-a567-0e02b2c3d479"UUID v7:新标准(RFC 9562)
UUID v7 在 RFC 9562(2024)中最终确定,专门设计用作数据库键。它将 48 位 Unix 时间戳(毫秒精度)与 74 位随机数结合,生成与现有 UUID 基础设施完全兼容的时间有序 UUID。
优势:
- 时间有序——自然按创建时间排序
- 优秀的数据库插入性能(无索引碎片化)
- 与所有现有的 UUID 列和工具兼容
- PostgreSQL 17+ 原生支持
劣势:
- 随机位更少(74 vs 122)——但仍具有极高的抗碰撞性
- 时间戳被嵌入——会暴露大致的创建时间
- 库支持仍在增长中(但增速很快)
// Node.js (uuid library v9+)
import { v7 as uuidv7 } from 'uuid';
uuidv7();
// → "018e4f5c-6a7b-7000-8000-1234abcd5678"
// ^^^^^^^^^^^^^^^^ timestamp portionULID:可字典序排序
ULID(Universally Unique Lexicographically Sortable Identifier)早于 UUID v7,并共享类似的设计:48 位时间戳 + 80 位随机数。关键区别在于其编码方式:Crockford Base32,生成紧凑的 26 字符字符串,如 01ARZ3NDEKTSV4RRFFQ69G5FAV。
优势:
- 紧凑的字符串表示(26 个字符 vs UUID 的 36 个)
- 内置单调性——在同一毫秒内生成的 ID 仍然有序
- 作为字符串可字典序排序(无需特殊比较)
- 大小写不敏感且 URL 安全
劣势:
- 不是 RFC 标准——仅为社区规范
- 不经过转换则与 UUID 列不兼容
- 比 UUID 的库支持更少
// JavaScript
import { ulid } from 'ulid';
ulid();
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"NanoID:紧凑且可定制
NanoID 采用了不同的方法:它不使用固定格式,而是生成紧凑的、URL 安全的随机字符串,具有可配置的字母表和长度。默认值为 21 个字符,使用 A-Za-z0-9_-。
优势:
- 非常紧凑(默认 21 个字符,可定制)
- 默认 URL 安全
- 可定制的字母表和长度
- 极小的库(gzip 后约 130 字节)——非常适合前端
- 加密强度高(使用 crypto.getRandomValues)
劣势:
- 无时间排序——与 UUID v4 存在相同的 B-tree 碎片化问题
- 没有标准规范
- 与 UUID 基础设施不兼容
// JavaScript
import { nanoid } from 'nanoid';
nanoid();
// → "V1StGXR8_Z5jdHi6B-myT"决策指南:你应该选择哪个?
在以下情况使用 UUID v7:
- 使用关系型数据库构建新应用
- 需要时间有序的 ID 以实现高效索引
- 系统期望标准 UUID 格式(PostgreSQL、MySQL 等)
- 希望获得兼容性和性能的最佳平衡
在以下情况使用 UUID v4:
- 使用仅支持 UUID v4 的系统
- 需要最大随机性且不泄露时间戳
- 数据库规模为中小型(< 100 万行)
在以下情况使用 ULID:
- 需要更短的字符串表示
- 使用按字典序排序字符串的系统
- 同一毫秒内的单调性很重要
- 不需要 UUID 列兼容性
在以下情况使用 NanoID:
- 在浏览器或前端生成 ID
- 包大小很重要(NanoID 仅 130 字节)
- 需要短小的、URL 友好的标识符
- 构建 URL 缩短器、邀请码或会话令牌
性能基准测试:数据库插入
这些格式之间最具影响力的区别是数据库插入性能。时间有序的 ID(UUID v7、ULID)保持顺序 B-tree 插入,而随机 ID(UUID v4、NanoID)导致随机页面分裂:
| ID 格式 | 100 万次插入(PostgreSQL) | 索引大小 | 插入模式 |
|---|---|---|---|
| 自增 | ~12s (baseline) | 21 MB | Sequential |
| UUID v7 | ~15s (+25%) | 56 MB | Nearly sequential |
| ULID | ~15s (+25%) | 56 MB | Nearly sequential |
| UUID v4 | ~28s (+133%) | 56 MB | Random |
| NanoID (21 chars) | ~30s (+150%) | 62 MB | Random |
基准测试数据为近似值,会因硬件、数据库版本和表结构而异。相对差异在所有环境中保持一致。
迁移路径:UUID v4 到 UUID v7
如果你正在考虑从 UUID v4 迁移到 UUID v7,好消息是它们共享相同的 128 位格式和字符串表示。在大多数数据库中,你可以开始为新行生成 UUID v7 值,同时现有的 UUID v4 值保持有效:
-- PostgreSQL 17+
CREATE TABLE users (
id UUID DEFAULT gen_random_uuid_v7() PRIMARY KEY,
name TEXT NOT NULL
);
-- Older PostgreSQL with uuid-ossp or pgcrypto
-- Use application-level UUID v7 generation常见问题
我应该从 UUID v4 迁移到 UUID v7 吗?
如果你的应用使用 UUID 作为数据库主键,并且在大规模情况下遇到索引碎片化或插入缓慢的问题,那么迁移到 UUID v7 值得考虑。对于中小型应用,UUID v4 工作正常。
NanoID 适合用作数据库主键吗?
NanoID 具有加密安全性和可配置长度,但缺少时间排序。对于插入性能很重要的数据库主键,UUID v7 或 ULID 是更好的选择。NanoID 在前端应用和 URL 缩短器中表现出色。
UUID v7 和 ULID 之间有什么区别?
两者都是时间有序的唯一标识符。UUID v7 遵循 RFC 9562,与现有 UUID 基础设施兼容(128 位,标准格式)。ULID 使用 Crockford Base32 编码(26 个字符),并在同一毫秒内具有内置单调性。UUID v7 更适合期望 UUID 格式的系统;ULID 作为字符串更紧凑。