生成唯一标识符时,UUID 是默认选择。但选 v4 还是 v7?这个问题在 2024 年 RFC 9562 发布后变得重要起来。v7 不是简单的"新版本",它针对 v4 在数据库场景的核心痛点做了根本性改进。选错了,你的主键索引可能在数据量增长后开始碎片化,写入性能下降几个数量级。
这篇指南对比两者的内部结构、性能特征和适用场景,帮你做出选择。
UUID v4:纯随机的选择
v4 是迄今为止最常用的 UUID 版本。它的 128 位中,有 122 位是随机数,剩下 6 位是版本和变体标识。
// 标准 v4 生成
const uuid = crypto.randomUUID();
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"
v4 的优势:
- 实现简单:标准库到处都有,
crypto.randomUUID()在所有现代浏览器和 Node.js 中可用 - 无需协调:两个独立服务生成的 v4 几乎不可能冲突
- 不泄露时间信息:从 UUID 看不出创建顺序
v4 的劣势主要在数据库场景,下面详述。
UUID v7:时间有序的新标准
v7 在 2024 年由 RFC 9562 标准化。它的结构很不一样:
- 48 位:Unix 毫秒时间戳(约 8900 年的范围)
- 4 位:版本标识(7)
- 12 位:随机或计数器(同毫秒内排序)
- 2 位:变体标识
- 62 位:随机数
// v7 生成(需要支持库,如 uuid v11+)
import { v7 } from 'uuid';
const id = v7();
// "017f22e2-79b0-7cc3-98c4-dc0c0c07398f"
注意前几位是十六进制时间戳,所以 v7 UUID 是按生成时间自然排序的。
为什么有序性对数据库很重要
这是 v7 存在的核心原因。
大多数数据库(PostgreSQL、MySQL、SQL Server)的主键索引用 B 树。B 树在有序插入时性能最优。v4 是完全随机的,每次插入都落在 B 树的随机位置,导致:
- 页分裂:插入位置已满时,B 树节点要分裂,触发额外磁盘 IO
- 缓存失效率:随机写入让缓存命中率下降,叶子页频繁换入换出
- 碎片化:随着时间推移,索引物理布局越来越散,范围扫描性能下降
- 聚簇索引问题:在 InnoDB 这类聚簇存储引擎中,主键就是物理顺序,v4 让每行写入都触发数据物理移动
v7 解决了这些问题。同一时间窗口内生成的 UUID 在 B 树中是连续的,写入模式从"随机"变成"近似顺序追加"。
实测影响很显著。在 PostgreSQL 上插入 1000 万行的对比测试中,v7 通常比 v4 快 2 到 5 倍,索引体积也小 30% 以上。具体数字取决于硬件和配置,但方向是确定的。
碰撞概率:v4 真的不会撞吗
v4 的 122 位随机性意味着碰撞概率极低但不是零。生日悖论告诉我们,生成约 2^61(约 2.3 × 10^18)个 v4 UUID 后,有 50% 概率出现一次碰撞。
这个数字远超任何实际系统的规模。但对"每秒生成数百万 ID"的高频系统,长期运行的累积概率仍值得考虑。v7 把时间戳前置后,碰撞窗口缩小到"同一毫秒内的随机部分",配合单调计数器,碰撞概率进一步降低。
v4 vs v7 对比
| 维度 | UUID v4 | UUID v7 | |------|---------|---------| | 随机位数 | 122 | 74(外加时间戳) | | 时间排序 | ✗ | ✓ | | 数据库索引友好 | ✗(随机写入) | ✓(近似顺序) | | 信息泄露 | 不泄露时间 | 可推断创建时间 | | 标准化年份 | 2005 (RFC 4122) | 2024 (RFC 9562) | | 库支持 | 无处不在 | 主流库均已支持 | | 可排序查询 | 需要 created_at 列 | 直接 ORDER BY id |
何时选 v4
- 纯无状态系统:日志条目 ID、缓存键、临时令牌,不需要排序
- 安全敏感场景:不希望从 ID 暴露创建时间(虽然这种"安全"通常很弱,时间戳可以从其他渠道推断)
- 遗留系统兼容:迁移到 v7 需要评估现有代码假设
- 跨系统协调:与仍在用 v4 的外部系统对接
何时选 v7
- 任何使用数据库主键的场景:这是 v7 设计的核心目标
- 事件日志和审计:天然按时间排序,省去额外的 created_at 索引
- 高吞吐写入系统:减少 B 树碎片化带来的 IO 放大
- 需要"按时间排序但不想暴露精确时间"的场景:可以把时间戳位混淆或加密
实际迁移注意事项
如果你打算从 v4 迁移到 v7,注意几点:
- 存储格式不变:两者都是 128 位,UUID 列类型无需修改
- 现有数据保留原值:不要回填,新数据用 v7 即可
- 排序语义变化:原来按 created_at 排序的查询,可以考虑按 id 排序(前提是表里全是 v7)
- 索引重建:迁移后建议重建一次主键索引,让旧 v4 数据重新组织
-- PostgreSQL:检查主键索引膨胀情况
SELECT pg_size_pretty(pg_relation_size('your_table_pkey'));
-- 必要时重建
REINDEX INDEX your_table_pkey;
在本地生成 UUID
无论你需要 v4 还是 v7,都可以在不依赖服务端的情况下生成。
UUID 生成器 支持在浏览器中批量生成 UUID,纯前端运行,没有服务端请求。你的生成结果(尤其是用作内部标识符或测试数据时)不会上传到任何地方。
命令行快速生成:
# Node.js(v4)
node -e "console.log(crypto.randomUUID())"
# Python(v4,需要 uuid 模块)
python3 -c "import uuid; print(uuid.uuid4())"
# v7 需要第三方库
npx uuidv7
总结:纯无状态系统选 v4 没问题;涉及数据库、需要按时间排序的场景,v7 是 2026 年的合理默认。RFC 9562 已经把 v7 标准化两年了,主流库支持完整,新项目没有理由再为 v4 的索引碎片化付出代价。