GUID 版本详解:v1–v7 该选哪个?
2026-07-09
Tags: Windows · GUID · 科普 · QuickGUID
打开任何一个 UUID 库,你会看到 uuid1()、uuid3()、uuid4()、uuid5(),以及越来越常见的 uuid7()。规范一共定义了八个版本。你到底该用哪一个?
简短回答:绝大多数场景用 v4,数据库主键用 v7,需要相同输入每次都生成同一个 GUID 时用 v5。 本文剩下的部分解释原因 —— 附带字节布局、每个版本存在的真实理由,以及一份按语言分类的代码速查表。
如果你是 GUID 新手,建议先看 什么是 GUID? 了解结构和唯一性的基础。本文是它的技术姊妹篇,对每个版本深入展开。
GUID/UUID 版本全景
每个版本都是填充同样 128 位的另一种 配方。版本 nibble(第三段的首位十六进制数字)告诉你用了哪种配方。
| 版本 | 生成来源 | 可排序 | 典型用途 |
|---|---|---|---|
| v1 | 时间 + MAC | 是 | 遗留系统(隐私风险) |
| v2 | DCE Security | 是 | 罕见,专用 |
| v3 | MD5 命名空间哈希 | 否 | 已弃用(用 v5) |
| v4 | 随机 | 否 | 通用默认值 |
| v5 | SHA-1 命名空间哈希 | 否 | 确定性 ID |
| v6 | 重排后的 v1 时间 | 是 | 直接替换 v1 |
| v7 | 毫秒时间戳 | 是 | 数据库主键 |
| v8 | 厂商自定义 | 自定义 | 专用 |
v1、v4、v5、v7 覆盖了 99% 的实际使用。下面逐一展开。
v1:时间与 MAC 地址
v1 是 RFC 4122 的原始配方。它把三样东西塞进 128 位:
| time_low (32) | time_mid (16) | ver+time_hi (16) | var+clk_seq (16) | node/MAC (48) |- 60 位时间戳 —— 自 1582-10-15(格里高利历采用日)起的 100 纳秒间隔。时间戳分散在
time_low、time_mid、time_hi中。 - 14 位时钟序列 —— 一个计数器,用于区分同一时钟节拍内生成的两个 GUID,或系统时钟被回调的情况。
- 48 位节点 —— 网卡的 MAC 地址。
MAC 地址的问题
节点字段是 硬件指纹。你发布的每一个 v1 GUID 都带着你机器的 MAC 地址 —— 而且与 IP 地址不同,MAC 地址被设计为全局唯一且持久不变。这是真实的隐私泄露。
这不是理论问题。研究人员已经证明,通过读取文件元数据中嵌入的 v1 GUID,就能把文档追溯到创建它的那台机器。微软自己在这一情况被演示之后,就把 v1 生成从 Office 遥测中移除了。现代操作系统出于同样原因,也会对展示给 Wi-Fi 网络的 MAC 做随机化。
结论: 新代码避免使用 v1。它在遗留系统、COM 接口和某些硬件相关工具里幸存 —— 但今天没有任何理由自己生成它。
v2:DCE Security
v2 是你基本上永远不会遇到的版本。它沿用 v1 的布局(时间戳 + MAC),在其上叠加一层 POSIX UID/GID —— 即 DCE(分布式计算环境)安全模型中的本地用户和组标识符 —— 使 UUID 在唯一性之外还能携带访问控制信息。
v2 专为遗留企业系统中的 DCE/RPC 认证和 ACL 而存在。除非你在维护明确使用 DCE Security 的软件,否则你永远不会生成或消费 v2。它在这里出现纯粹是为了让版本列表完整,免得你某天看到那个 nibble 里的 "2" 时犯嘀咕。
v3:为什么它被弃用
v3 与 v5 完全相同,只是用 MD5 替代 SHA-1。MD5 存在实际碰撞攻击 —— 两个不同输入可以被精心构造出同一个哈希。这在对抗场景下破坏了 v3 的确定性保证。
RFC 4122 为了向后兼容保留 v3,但 v5 已经取代了它。今天没有任何理由生成 v3。如果在野外遇到 v3,把它当遗留处理。
v4:随机的默认值
v4 是几乎所有 "生成 GUID" 调用 [guid]::NewGuid()、Guid.NewGuid()、crypto.randomUUID()、uuid.uuid4()、NEWID() 返回的东西。它用密码学强随机填充 128 位中的 122 位(其余 6 位是版本和变体标记)。
没有时间戳、没有 MAC、没有机器状态 —— 纯随机。这让 v4 可以安全暴露、并行化极其简单,且对任何现实工作负载都具有足够的抗碰撞能力。完整的碰撞数学(以及网上流传的 "十亿年" 说法为什么把生日悖论搞错了)见 主文章。
结论: 这就是你的默认值。除非有具体理由选别的,否则就选 v4。
v5:确定性命名空间 ID
这是大多数开发者听说过却从没真正用过的版本 —— 而它解决了一个真正有用的问题。
v5 让你能从 相同 的输入每次都推导出 相同 的 GUID。给它一个命名空间 UUID 和一个名称字符串,它返回一个确定性标识符:
import uuid
# 相同输入 → 相同 GUID,每次运行,每台机器
dns_guid = uuid.uuid5(uuid.NAMESPACE_DNS, 'example.com')
url_guid = uuid.uuid5(uuid.NAMESPACE_URL, 'https://prunelab.net/blog/guid-explained')
print(dns_guid) # 始终 cf14e0a3-...(同一个值)
print(dns_guid == uuid.uuid5(uuid.NAMESPACE_DNS, 'example.com')) # True工作原理
在内部,v5 把命名空间 UUID 字节与名称的 UTF-8 字节拼接起来,对结果运行 SHA-1,取前 16 个字节,然后盖上版本(5)和变体位。命名空间本身就是一个 UUID —— NAMESPACE_DNS、NAMESPACE_URL、NAMESPACE_OID、NAMESPACE_X500 是预定义的,但任何 UUID 都能当命名空间用。
v5 什么时候是对的工具
- 可复现的测试夹具 —— 为测试数据生成跨运行稳定的 ID,让失败的测试每次指向同一个实体。
- URL 派生标识符 —— 不查数据库就把一个 URL 变成 GUID。两个系统分别独立计算同一个 URL 的
uuid5(NAMESPACE_URL, url),结果永远一致。 - 幂等去重 —— 同一条记录导入两次,GUID 与自身碰撞,于是光靠键就能识别重复。
与 v4 的关键对照:v4 每次调用返回一个唯一值(即便输入相同);v5 对相同输入返回稳定值。两者不可互换。
结论: 每当需要一个 可复现地 从某输入字符串推导出来的 GUID 时,就用 v5。
v6:你大概用不到的 v1 修正版
v6 包含与 v1 完全相同的数据 —— 同样的时间戳、时钟序列和 MAC —— 但重新组织时间戳位,让数值能自然按时间先后排序。在 v1 里时间戳被笨拙地拆分(low、mid、high);在 v6 里它按 high-first 存储,所以字符串的字典排序就等于时间排序。
v6 于 2024 年与 v7 一起在 RFC 9562 中标准化。它主要作为 直接迁移路径,服务于那些已经绑定 v1 时钟加 MAC 模型的系统:保留现有的时间戳逻辑,只重写位布局即可。
对于新系统,v7 几乎总是更好的选择。 v7 用毫秒时间戳(比 v1/v6 的 100 纳秒格里高利计数更简单),不嵌入 MAC 地址,随机填充也更干净。v6 回答的是 "如何在不改变生成模型的前提下现代化 v1";v7 回答的是 "今后该用什么"。
v7:面向数据库的时间排序
v7 是自 v4 以来最重要的新增版本。它解决 v4 在数据库里造成的问题。
数据库里的 v4 问题
当数据库用 GUID 做主键时,这些键支撑一个 B+ 树索引。当插入大致有序时,B+ 树保持高效 —— 新值落在右边缘,填充已有页。随机 v4 GUID 散落在整个键空间里,于是每次插入都命中一个随机页。在大表上这会导致频繁的页分裂、索引碎片化、缓存颠簸和写放大。SQL Server 的解法(NEWSEQUENTIALID())是一个专有的、不可移植的 hack,而且会泄露顺序信息。
v7 的布局
| unix_ts_ms (48) | ver (4) | rand_a (12) | var (2) | rand_b (62) |- 48 位毫秒级 Unix 时间戳 —— 首字段,所以数值随时间增长。
- 4 位版本 —— 设为
7。 - 12 位 rand_a —— 随机,或者可选地用一个单调计数器在同一毫秒内排序。
- 2 位变体 —— 标准 RFC 9562 标记。
- 62 位 rand_b —— 随机。
由于时间戳在前,连续生成的 v7 GUID 天然有序:索引插入集中在右边缘,页干净地填满,B+ 树表现就像在用自增整数一样 —— 同时仍然全局唯一,不需要中央协调器。
什么时候 v7 不值得
- 内存存储(Redis、memcached)—— 没有 B+ 树,没有碎片化代价。v4 就行。
- 小表 —— 在百万行以下,v4 与 v7 的差异看不见。
- 非键列 —— 如果 GUID 只是被存储的属性而非索引键,顺序无关。
- 必须不透明的 GUID —— v7 会泄露生成的毫秒数,在安全敏感场景下可能不受欢迎。
结论: 如果 GUID 是真实数据库上的聚簇主键,用 v7。否则 v4。
v8:厂商自定义
v8 是逃生舱:RFC 9562 允许厂商自定义位布局,只要版本 nibble 为 8 且变体位正确即可。其余一切 —— 时间戳位宽、随机性来源、编码方式 —— 都由实现者决定。
你只会在那些为特定原因设计了自定义 UUID 方案的系统里见到 v8(比如把分片 ID 或节点 ID 烤进 UUID 用于路由)。如果你不是在建这样的系统,v8 与你无关;如果你是,规则由你自己的方案定义,而不是这篇文章里的任何东西。
你到底该用哪个版本?
一份决策指南:
- "生成通用 ID?" → v4。 这是 90% 情况下的答案。
- "数据库主键,或需要时间排序?" → v7。 特别是大表上的聚簇索引。
- "需要相同输入每次都得到相同 GUID?" → v5。 从字符串做确定性映射。
- "在与一个期望 v1 的遗留系统打交道?" → v1(保持兼容),但规划迁移到 v7。
- "有人建议我用 v3?" → 别。改用 v5(v3 的 MD5 已经被攻破)。
- "我想要最新的。" → 这不是理由。v7 新 而且 解决了真实问题(数据库排序);v6 新 而且 主要是 v1 的迁移助手。按用例选,不按新旧选。
如果还是拿不准,就用 v4。默认值的存在是有理由的。
按版本和语言生成 GUID
| 语言 / 工具 | v4(默认) | v5(确定性) | v7(有序) |
|---|---|---|---|
| PowerShell | [guid]::NewGuid() | — | — |
| C# / .NET | Guid.NewGuid() | — | Guid.CreateVersion7() (.NET 9+) |
| Python | uuid.uuid4() | uuid.uuid5(uuid.NAMESPACE_URL, 'name') | uuid.uuid7() (3.14 beta) / 库 |
| JavaScript | crypto.randomUUID() | — | (npm uuid v11+) |
| Node.js (uuid 库) | uuid.v4() | uuid.v5('name', NAMESPACE) | uuid.v7() |
| SQL Server | NEWID() | — | — |
| PostgreSQL | gen_random_uuid() | — | (扩展) |
几点说明:截至 2026 年,v7 的原生支持仍在陆续落地到标准库。Node.js 的 uuid 包(v11+)和 .NET 9 原生提供;Python 在 3.14 加入 uuid.uuid7();更老的运行时需要库。v5 支持几乎无处不在,因为它纯粹是 SHA-1 数学,不需要熵源。
常见问题
v7 会取代 v4 成为默认值吗?
不会。v4 仍是通用默认。v7 只在 GUID 是有序主键时胜出 —— 对大多数其他用途(会话令牌、请求 ID、内容哈希、一切非数据库场景),v4 的随机性不是缺点,而 v7 的首位时间戳只会在没有收益的情况下泄露时序信息。
v1 现在还安全吗?
不安全。v1 嵌入你网卡的 MAC 地址,那是一个持久的硬件指纹。微软自己在研究人员通过文档里嵌入的 GUID 追踪到机器之后,就把 v1 从 Office 遥测里移除了。新代码完全避免 v1;遗留 v1 则规划迁移到 v7。
v3 和 v5 有什么区别?
两者都是确定性命名空间哈希(相同输入 → 相同 GUID)。v3 用 MD5,v5 用 SHA-1。MD5 有实际碰撞攻击,所以 v3 在完整性用途上被视为已被攻破,被 v5 取代。今天没有理由生成 v3。
能把 v4 GUID 转换成 v7 吗?
不能。版本不是可以随便换的标签 —— 每个版本使用完全不同的生成逻辑。v4 没有时间戳可提取,所以 "得到一个 v7" 的唯一办法是生成一个新的。如果要把一个 v4 主键列迁移到 v7,实际上是加一个新列并重写它。
.NET 原生支持 v7 吗?
支持。Guid.CreateVersion7() 在 .NET 9(2024 年 11 月发布)中加入。如果你在 .NET 8 或更早版本上,使用 GenVault 或 UUIDv7 NuGet 包,或者升级。
QuickGUID:生成 v4 和 v7,转换任意格式
对于日常的 GUID 工作,QuickGUID 是一款原生 Windows 工具箱,覆盖完整工作流:
- 批量生成 v4 或 v7,一次最多 1,000 个
- 10+ 格式转换:标准、无连字符、花括号、Base64、C 字节数组、
DEFINE_GUID宏 —— 全部带实时预览 - 智能提取:粘贴日志或源代码,自动找到并转换每一个 GUID
- 实时装饰:引号、前缀/后缀、大小写 —— 全局切换后直接复制
完全免费,100% 离线。
结尾
版本系统之所以存在,是因为不同的工作需要不同的保证:v4 给你不可追踪的随机性,v5 给你可复现性,v7 给你有序性。默认选 v4,在数据库键上用 v7,当相同输入必须产生相同 GUID 时用 v5。
GUID 新手?先看 什么是 GUID?,了解结构、连字符格式,以及为什么 128 位足以让碰撞在实际中不成为问题。