Skip to content

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遗留系统(隐私风险)
v2DCE Security罕见,专用
v3MD5 命名空间哈希已弃用(用 v5)
v4随机通用默认值
v5SHA-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_lowtime_midtime_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 和一个名称字符串,它返回一个确定性标识符:

python
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_DNSNAMESPACE_URLNAMESPACE_OIDNAMESPACE_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# / .NETGuid.NewGuid()Guid.CreateVersion7() (.NET 9+)
Pythonuuid.uuid4()uuid.uuid5(uuid.NAMESPACE_URL, 'name')uuid.uuid7() (3.14 beta) / 库
JavaScriptcrypto.randomUUID()(npm uuid v11+)
Node.js (uuid 库)uuid.v4()uuid.v5('name', NAMESPACE)uuid.v7()
SQL ServerNEWID()
PostgreSQLgen_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 或更早版本上,使用 GenVaultUUIDv7 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 位足以让碰撞在实际中不成为问题。