什么是 UUID?格式解析、版本详解与实际应用完全指南
从零理解 UUID:128 位结构、十六进制格式解析、v1/v3/v4/v5/v7 内部原理、碰撞概率计算、真实应用场景、安全注意事项,以及多语言代码示例。
什么是 UUID?格式解析、版本详解与实际应用完全指南
每次你注册一个服务,系统都会为你的账号创建一个唯一标识符。每个 API 请求携带一个追踪 ID。分布式数据库中的每一行都需要一个不会与其他机器生成的主键冲突的标识。这一切背后的解决方案?UUID — 通用唯一标识符(Universally Unique Identifier)。
本文将从头解释 UUID 是什么、它的结构如何组成、每个版本底层做了什么,以及何时应该使用(或避免)它们。
UUID 概览
UUID 是一个 128 位(16 字节)的标识符,设计目标是在不需要中央权威机构的情况下实现全局唯一。它以 32 个十六进制字符写成,采用标准的 8-4-4-4-12 格式:
550e8400-e29b-41d4-a716-446655440000
|------| |--| |--| |--| |----------|
8 位 4 4 4 12 位
即 32 个十六进制字符 + 4 个连字符 = 总共 36 个字符。连字符纯粹是为了可读性,不携带数据。
核心事实:
- 128 位 = 2¹²⁸ ≈ 3.4 × 10³⁸ 种可能的值
- 由 RFC 9562 标准化(2024 年 5 月,取代 RFC 4122)
- 在 Microsoft 生态中也称为 GUID(Globally Unique Identifier)——格式相同,名称不同
- PostgreSQL(
uuid类型)、MySQL(BINARY(16)或CHAR(36))以及几乎所有编程语言都原生支持
UUID 的内部结构
每个 UUID 在固定的位位置编码两个元数据字段,与版本无关:
550e8400-e29b-41d4-a716-446655440000
^ ^
| |
版本号--┘ └--变体标识
版本字段(第 48–51 位)
第 13 个十六进制字符(第三组的第一位)标识 UUID 版本:
| 十六进制值 | 版本 | 生成方式 |
|---|---|---|
1 | v1 | 时间戳 + MAC 地址 |
3 | v3 | namespace + name 的 MD5 哈希 |
4 | v4 | 密码学安全随机数 |
5 | v5 | namespace + name 的 SHA-1 哈希 |
6 | v6 | 重排序时间戳(RFC 9562) |
7 | v7 | Unix 时间戳 + 随机数(RFC 9562) |
8 | v8 | 自定义 / 实现特定 |
变体字段(第 64–65 位)
第 17 个十六进制字符(第四组的第一位)标识变体。对于 RFC 4122/9562 的 UUID,前两位是 10,因此这个字符只会是 8、9、a 或 b。
解析示例
550e8400-e29b-41d4-a716-446655440000
↑ ↑
4 → v4 a → RFC 4122 变体
这是一个 UUID v4(随机),符合 RFC 4122/9562 变体。
UUID 各版本详解
版本 1:时间戳 + MAC 地址
UUID v1 是最初的设计,编码了:
- 60 位时间戳 — 自 1582 年 10 月 15 日(格里高利历改革)以来的 100 纳秒间隔数
- 14 位时钟序列 — 单调性计数器,防止时钟回拨时产生重复
- 48 位节点 — 通常是机器的 MAC 地址
| 时间戳 | 版本 | 时钟序列 |变体| 节点(MAC) |
| 60 bits | 4b | 14b |2b | 48 bits |
问题:
- 泄露生成时间和硬件身份(隐私风险)
- MAC 地址可以被伪造,削弱唯一性
- 1582 纪元容易混淆,需要转换
结论: 已被 RFC 9562 弃用。需要基于时间的 UUID 请使用 v7。
版本 3:MD5 基于名称的(确定性)
UUID v3 使用 MD5 对 namespace UUID 和 name 字符串 进行哈希。相同的输入始终产生相同的 UUID。
import uuid
# namespace = DNS, name = "example.com"
print(uuid.uuid3(uuid.NAMESPACE_DNS, "example.com"))
# → "9073926b-929f-31c2-abc9-fad77ae3e8eb"(始终是这个值)
标准定义了四个命名空间:
- DNS:
6ba7b810-9dad-11d1-80b4-00c04fd430c8 - URL:
6ba7b811-9dad-11d1-80b4-00c04fd430c8 - OID:
6ba7b812-9dad-11d1-80b4-00c04fd430c8 - X.500:
6ba7b814-9dad-11d1-80b4-00c04fd430c8
结论: 功能可用,但建议优先使用 v5——SHA-1 比 MD5 更强。
版本 4:随机——最流行的版本
UUID v4 用密码学安全的随机数填充 122 位(剩余 6 位保留给版本和变体字段)。
| 随机数 | 版本 | 随机数 |变体| 随机数 |
| 48 bits | 4b | 12 bits |2b | 62 bits |
2¹²² ≈ 5.3 × 10³⁶ 种可能值,碰撞概率极低。要达到 50% 的碰撞概率,你需要大约 2.71 × 10¹⁸ 个 UUID——即 271 京。
// 所有现代浏览器和 Node.js 都支持
const id = crypto.randomUUID();
console.log(id); // → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
优势: 简单、隐私、全平台支持、无需协调。
劣势: 随机分布在用作数据库主键时会导致 B-tree 索引碎片化。数据库密集场景建议考虑 v7。
版本 5:SHA-1 基于名称的(确定性)
与 v3 相同但使用 SHA-1 替代 MD5。相同输入始终产生相同 UUID。
import uuid
print(uuid.uuid5(uuid.NAMESPACE_DNS, "example.com"))
# → "cfbff0d1-9375-5685-968c-48ce8b15ae17"(始终是这个值)
应用场景:
- 从 URL 或 DNS 名称生成稳定 ID
- 内容寻址存储的键
- 可重复的测试数据
重要提示: v3 和 v5 不是为安全而设计的。它们是确定性的——任何知道 namespace 和 name 的人都可以重现 UUID。
版本 7:Unix 时间戳 + 随机数(新项目推荐)
UUID v7 是最新版本,由 RFC 9562(2024 年 5 月) 引入。它编码了:
- 48 位 Unix 时间戳(毫秒)——单调递增
- 74 位密码学随机数
| Unix 时间戳(毫秒) | 版本 | rand_a |变体| rand_b |
| 48 bits | 4b | 12 bits |2b | 62 bits |
这意味着 v7 UUID 天然按创建时间排序——较新的 UUID 在字典序上始终大于较旧的。这一特性使其非常适合用作数据库主键,B-tree 索引保持顺序而非随机碎片化。
import { v7 as uuidv7 } from "uuid";
const id1 = uuidv7(); // 在 T₁ 时刻生成
const id2 = uuidv7(); // 在 T₂ 时刻生成(T₂ > T₁)
console.log(id1 < id2); // → true(字典序比较)
对数据库的意义: v7 的顺序特性比 v4 减少最高 90% 的索引页分裂,带来更快的插入、更小的索引和更好的缓存命中率。
UUID vs GUID — 有什么区别?
没有功能上的区别。GUID(Globally Unique Identifier)是微软对 UUID 的称呼,用于 Windows、.NET、COM 和 SQL Server。格式完全相同:128 位,8-4-4-4-12 十六进制。
唯一的外观差异:微软工具有时以大写加花括号显示 GUID:
UUID: 550e8400-e29b-41d4-a716-446655440000
GUID: {550E8400-E29B-41D4-A716-446655440000}
如果有人问”UUID 和 GUID 的区别”,答案是:品牌命名不同。
特殊 UUID 值
RFC 9562 定义了两个特殊 UUID:
| 名称 | 值 | 用途 |
|---|---|---|
| Nil UUID | 00000000-0000-0000-0000-000000000000 | 表示值的缺失(类似 null) |
| Max UUID | ffffffff-ffff-ffff-ffff-ffffffffffff | 边界标记或哨兵值 |
永远不要将这些用作实际标识符——它们按定义不具有唯一性。
碰撞概率:生日问题
“生日问题”计算的是需要多少个 UUID 才能使碰撞变得可能。对于 UUID v4(122 位随机数):
| 已生成 UUID 数量 | 碰撞概率 |
|---|---|
| 100 万 | ~10⁻²²(几乎不可能) |
| 10 亿 | ~10⁻¹⁶(仍然可忽略) |
| 2.71 × 10¹⁸ | 50%(“生日界限”) |
换个说法:如果你每秒生成 10 亿个 UUID,需要 86 年才能达到 50% 的碰撞概率。在实践中,硬件故障、软件 bug 和宇宙射线导致重复的可能性,都远大于 UUID v4 的数学碰撞概率。
公式:p(n) ≈ n² / (2 × 2¹²²)
如何验证 UUID
合法的 UUID 匹配以下正则表达式(不区分大小写):
^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
这检查了:
- 8-4-4-4-12 的十六进制格式
- 版本位是 1–7(第 15 位)
- 变体位以 8、9、a 或 b 开头(第 20 位)
function isValidUUID(str) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(str);
}
isValidUUID("550e8400-e29b-41d4-a716-446655440000"); // → true
isValidUUID("not-a-uuid"); // → false
各语言生成 UUID
JavaScript / TypeScript
// 浏览器和 Node.js — 内置 v4
crypto.randomUUID();
// npm uuid 包 — 支持 v1、v3、v4、v5、v7
import { v4, v7 } from "uuid";
v4(); // 随机
v7(); // 时间有序
Python
import uuid
uuid.uuid4() # 随机
uuid.uuid5(uuid.NAMESPACE_DNS, "example.com") # 确定性
# uuid.uuid7() 计划在 Python 3.14+ 中内置
Go
import "github.com/google/uuid"
uuid.New() // v4 随机
uuid.Must(uuid.NewV7()) // v7 时间有序
Java
import java.util.UUID;
UUID.randomUUID(); // v4 随机
// UUID v7:使用 com.fasterxml.uuid 或 JDK 21+ 的 java.util.UUID
SQL(PostgreSQL)
-- v4(PostgreSQL 13+)
SELECT gen_random_uuid();
-- v7(PostgreSQL 18+)
SELECT uuidv7();
常见应用场景
数据库主键
UUID 让你可以在任何地方生成 ID——应用层、客户端、边缘节点——无需数据库往返。这使离线优先架构成为可能,并简化了分布式系统。索引性能优先选 v7,不关心排序选 v4。
API 请求追踪
在入口点(网关、负载均衡器)为每个 API 请求分配一个 UUID,通过 X-Request-ID 等 header 传递给所有下游服务。这使得跨微服务的日志关联变得轻而易举。
幂等性键
API 使用 UUID 作为幂等性键,确保重试的请求不会创建重复资源。客户端在首次尝试前生成一个 UUID,并在重试时发送同一个 UUID。
会话标识符
UUID 提供了足够的唯一性来防止大规模用户群中的会话冲突。与自增整数不同,UUID 无法被枚举——攻击者不能通过递增数字来猜测有效的会话 ID。
内容寻址存储
UUID v5 从内容生成确定性 ID。给定相同的输入,你始终得到相同的 UUID——适用于去重、缓存和可重复构建。
安全注意事项
UUID 不是安全令牌
UUID 的设计目标是唯一性,而非保密性。关键问题:
- UUID v1 泄露生成时间戳和 MAC 地址
- UUID v4 有 122 位随机数,但结构可预测(版本/变体位是固定的)
- UUID v3/v5 是确定性的——知道 namespace 和 name 的人可以重现 UUID
对于安全令牌、API 密钥或会话密钥,应使用专用的 CSPRNG 生成 128+ 位的纯随机数据:
// 安全令牌 — 不是 UUID,而是完全随机的
const token = Array.from(crypto.getRandomValues(new Uint8Array(32)))
.map(b => b.toString(16).padStart(2, "0"))
.join("");
UUID v7 暴露创建时间
UUID v7 的前 48 位编码了毫秒级的创建时间戳。任何收到 v7 UUID 的人都可以提取其创建时间:
const hex = "01906b5e-4a3e-7234-8f56-b8c12d4e5678".replace(/-/g, "").slice(0, 12);
new Date(parseInt(hex, 16));
// → 2024-07-01T12:34:56.000Z
如果创建时间是敏感信息,请使用 v4。
不要仅靠 UUID 防止枚举
虽然 UUID 比自增整数更难猜测,但它不应该是你唯一的访问控制机制。始终执行授权检查——不要依赖 URL 的模糊性。
常见问题
UUID 为什么有连字符?
8-4-4-4-12 格式中的连字符纯粹是为了方便人类阅读。它们不携带数据,解析时会被忽略。有些系统存储不带连字符的 UUID(32 个十六进制字符),同样有效。
两个 UUID 可能相同吗?
理论上可以,实际上不会。对于 122 位随机数的 UUID v4,任意一对 UUID 相同的概率约为 5.3 × 10³⁶ 分之一。在实际生成速率下,你被闪电击中同时中彩票的概率,都比遇到 UUID 碰撞的概率大。
UUID 是顺序的吗?
只有部分版本。UUID v1、v6、v7 包含时间戳,按时间顺序排列。UUID v4 完全随机,没有排序。UUID v3 和 v5 是确定性的但无序。
UUID 占用多少存储空间?
- 二进制:16 字节(128 位)——最高效的存储方式
- 字符串(带连字符):36 字节(ASCII)
- 字符串(无连字符):32 字节(ASCII)
大多数数据库内部以二进制格式存储 UUID。PostgreSQL 的原生 uuid 类型正好使用 16 字节。
主键应该用 UUID 还是自增整数?
单数据库应用用自增更简单(更小、更快、顺序)。分布式系统用 UUID 更好(随处生成、无需协调、合并安全)。如果使用 UUID,优先选 v7 以获得最佳数据库性能。
什么是 RFC 9562?
RFC 9562 于 2024 年 5 月发布,是最新的 UUID 标准。它取代 RFC 4122,正式引入 UUID 版本 6、7 和 8,弃用 v1 转而推荐 v6/v7,并定义了 nil UUID 和 max UUID。如果你在实现 UUID 生成或验证,RFC 9562 是权威参考。
UUID 可以跨编程语言使用吗?
可以。UUID 格式(128 位,8-4-4-4-12 十六进制)是语言无关的。在 JavaScript 中生成的 UUID 可以被 Python、Go、Java 或任何支持 UUID 的语言正确解析。这种互操作性是 UUID 最大的优势之一。
立即生成、解码和验证 UUID,试试我们的 UUID 生成器——支持 v1、v4、v5、v7 批量生成,100% 浏览器端运行。