UUID v4 与 v7:有什么变化以及为何重要
Source: Dev.to
请提供您希望翻译的具体文本内容,我将按照要求将其翻译成简体中文并保留原始的格式、Markdown 语法以及技术术语。谢谢!
📌 UUID v4 – 纯随机
f47ac10b-58cc-4372-a567-0d8b62691e10
^^^^
version 4
工作原理
- 生成 128 位随机数。
- 设置版本号(4 位 →
0100)。 - 设置变体(2 位 →
10)。 - 按
8‑4‑4‑4‑12的十六进制字符串格式化。
优点
- 极其简单,易于生成。
- 统计上唯一(2¹²² 种可能的取值)。
- 各系统之间无需协同。
- 不泄露信息(没有时间戳、没有 MAC 地址)。
缺点
- 完全随机 → 对数据库索引极为不友好。
- 没有自然顺序。
- 无法提取任何有用的元数据。
- 会导致数据库中 B‑树索引碎片化。
UUID v4 为我们提供了很好的服务,但“足够好”并不等同于“最优”。
🚀 UUID v7 – 时间有序、数据库友好
019526de-a3c0-7cc0-b2e8-4a1b3c5d7e9f
^^^^^^^^ ^^^^
timestamp version 7
工作原理
- 获取当前 Unix 时间戳(毫秒,48 位)。
- 设置版本号(4 位 →
0111)。 - 设置变体(2 位 →
10)。 - 用随机数据填充剩余的 62 位。
- 按标准 UUID 字符串格式化。
结构
| 位数 | 含义 |
|---|---|
| 48 | 时间戳(毫秒) |
| 4 | 版本(7) |
| 12 | 随机 a |
| 2 | 变体(10) |
| 62 | 随机 b |
优点
- 天然时间有序 – 字典序排序即为时间顺序。
- 数据库友好:顺序插入 → 最小化 B‑树页分裂。
- 可提取时间戳 – 能读取 UUID 的创建时间。
- 仍然全局唯一(62 位随机 + 毫秒时间戳)。
- 直接替换:同样的 128 位大小,同样的字符串格式。
权衡
- 泄露创建时间(前 48 位是时间戳)。
- 随机性略低于 v4(62 位 vs 122 位随机)。
- 较新 – 并非所有库都已支持。
📈 为什么主键的顺序很重要
当你在 B‑tree 索引的表(大多数表的默认索引方式)中使用 UUID v4 作为主键时,每次 INSERT 都会在索引的 随机位置 插入,导致:
- 页面分裂 – 不断进行 B‑tree 重组。
- 缓存未命中 – 没有局部性。
- 写放大 – 每次插入需要更多 I/O。
- 索引膨胀 – 页面碎片化,空间浪费。
在大规模环境下,这会严重影响性能。
UUID v7 解决了这些问题
由于 UUID v7 值是 时间有序 的,新的插入始终位于 B‑tree 的 末尾:
- 顺序写入 – 追加而非随机插入。
- 更好的缓存利用 – 最近的数据位于同一位置。
- 更少的页面分裂 – 树自然增长。
- 更小的索引 – 碎片更少。
基准测试始终显示,在 PostgreSQL 和 MySQL 上,使用 UUID v7 相比 UUID v4 的 INSERT 性能提升 2‑10 倍,具体取决于表的大小和工作负载。
✅ 何时使用哪个版本
| 场景 | 推荐 |
|---|---|
| 数据库主键 | v7 |
| API 请求追踪 | v7 |
| 会话令牌 | v4 |
| 事件溯源 | v7 |
| 密码重置令牌 | v4 |
| 分布式日志条目 | v7 |
| 缓存键(不需要排序) | v4 |
决策矩阵(快速浏览)
| 需求 | 使用 v4 | 使用 v7 |
|---|---|---|
| 零信息泄露(时间戳敏感) | ✅ | ❌ |
| 为机密/令牌提供最大熵 | ✅ | ❌ |
| 不需要排序(内存查找、哈希表) | ✅ | ❌ |
| 系统仅支持 v4 | ✅ | ❌ |
| ID 用作数据库主键(主要使用场景) | ❌ | ✅ |
| 需要自然的时间顺序 | ❌ | ✅ |
| 需要从 ID 中提取创建时间 | ❌ | ✅ |
| 分布式系统需要时间顺序的事件 | ❌ | ✅ |
| 关注大规模时的数据库性能 | ❌ | ✅ |
🛠️ 如何生成 UUID v7
Node.js (v20+)
import { randomUUID } from 'crypto';
// v4 only (as of Node 22)
const idV4 = randomUUID();
要生成 v7,请使用库:
import { v7 as uuidv7 } from 'uuid';
const idV7 = uuidv7();
Python
import uuid
# v4
uuid.uuid4()
# v7 (Python 3.14+ or uuid7 package)
from uuid_extensions import uuid7
uuid7()
PostgreSQL
-- v4 (built‑in)
SELECT gen_random_uuid();
-- v7 (PostgreSQL 17+ or pg_uuidv7 extension)
SELECT uuid_generate_v7();
在线
createuuid.com – 在浏览器中即时生成 v1、v4 和 v7,并支持批量生成。
🕵️ 从 UUID v7 中提取时间戳
function extractTimestamp(uuidV7) {
const hex = uuidV7.replace(/-/g, '');
const timestampHex = hex.substring(0, 12); // first 48 bits = 12 hex chars
const timestampMs = parseInt(timestampHex, 16);
return new Date(timestampMs);
}
// Example
extractTimestamp('019526de-a3c0-7cc0-b2e8-4a1b3c5d7e9f');
// → 2025‑02‑05T…
这在调试、审计以及在没有额外列或元数据的情况下理解数据流时非常有用。
🤔 我应该将现有的 v4 列迁移到 v7 吗?
简短回答: 可能不需要。
现有的 v4 UUID 正常工作。将生产数据库中的主键迁移的成本几乎从不值得。相反:
- 在新表和新服务中使用 v7。
- 在已有的地方保留 v4。
- 让系统的自然生命周期来完成过渡。
例外情况: 如果您在大规模下因 UUID v4 索引碎片导致真实的性能问题——并且已确认 UUID 是瓶颈——则可以考虑有计划的迁移。
📊 快速比较
| 功能 | UUID v4 | UUID v7 |
|---|---|---|
| 引入时间 | RFC 4122 (2005) | RFC 9562 (2024) |
| 随机位数 | 122 | 62 |
| 是否时间有序? | ❌ | ✅ |
| 大规模数据库性能 | 差 | 优秀 |
| 时间戳可读性? | ❌ | ✅ |
| 最适用场景 | 令牌、密钥 | 主键、事件 |
🎯 结论
- UUID v7 不是 v4 的替代品——它是针对另一种(且非常常见)工作场景的合适工具。
- 如果你正在启动新项目,并且你的 UUID 将用作 数据库主键,v7 是更好的默认选择。
- 对于 令牌、密钥,或任何不需要排序的情况,请坚持使用 v4。
现在就需要 UUID 吗?
试试 createuuid.com ——免费、即时、无需注册。
本文是 Developer Tools Deep Dives 系列的一部分,我们将在其中解释你每天使用的工具背后的“原因”。