UUID 完全指南:从 RFC 4122 到 RFC 9562,理解 v1~v8 的结构、生成算法与工程选型
在分布式系统中,生成 ID 看起来是一件很小的事,却涉及两个完全不同的问题:如何在不依赖中心节点的情况下避免标识冲突,以及这个 ID 在数据库和业务中是否好用。
MySQL 自增主键解决了单库内的编号问题,却很难直接满足多服务独立生成、离线创建、跨库合并等需求。UUID(Universally Unique Identifier,通用唯一标识符)提供了另一种思路:每个节点独立按照约定的算法生成 128 位标识符,不必先向统一的发号器申请号码。
不过,UUID 不等于随机字符串,UUID 的不同 Version 也并非单纯的软件版本升级。 有的 UUID 依赖时间和节点信息,有的依赖哈希,有的完全依赖随机数,还有的专门针对数据库的时间有序写入进行了设计。
本文结合 RFC 4122、RFC 9562 以及相关技术资料,把 UUID v1~v8 的结构、生成规则、适用场景和工程实践放在同一套框架中说明。
标准版本说明:2005 年发布的 RFC 4122 已被 2024 年发布的 RFC 9562 取代。本文以 RFC 9562 为当前规范,回溯 RFC 4122 对 v1~v5 的定义,并说明新增的 v6、v7、v8。UUIDv2 的具体生成格式源自 DCE Security 规范,RFC 9562 仅保留其编号,不重新定义其细节。
一、先给出结论:应该选哪个 UUID?
| 需求 | 优先考虑 | 原因与注意点 |
|---|---|---|
| 只需要分布式生成、不可预测性较好的普通标识符 | UUIDv4 | 122 位随机空间,成熟且广泛支持;用于 B-Tree 主键时插入局部性通常较差 |
| 新系统使用 UUID 作为数据库主键,希望大致按时间排列 | UUIDv7 | 48 位 Unix 毫秒时间戳位于高位,通常更利于索引写入 |
| 同一命名空间、同一名称必须得到相同 ID | UUIDv5 | 基于 Namespace + Name 的 SHA-1 确定性生成;不等于加密或签名 |
| 与既有 UUIDv1 系统兼容,但希望改善排序和索引局部性 | UUIDv6 | 重新排列 v1 的时间字段;纯新系统通常优先考虑 v7 |
| 维护历史上依赖 MAC/时间戳格式的系统 | UUIDv1 | 兼容优先;新系统一般不主动选择 |
| 维护 DCE Security / POSIX 身份编码系统 | UUIDv2 | 专用、少见、支持有限,不适合普通业务 ID |
| 兼容既有 MD5 名称型标识 | UUIDv3 | 新的名称型需求通常采用 v5 |
| 必须设计企业私有 UUID 位布局或 SHA-256 名称型标识 | UUIDv8 | 需要自行约定字段含义、唯一性和跨系统互操作规则 |
简化成一句话:不需要排序用 v4;需要时间有序的 UUID 优先看 v7;需要确定性映射用 v5;其他版本通常因为历史兼容或特殊协议约束才使用。
这只是选型起点,不意味着 v7 总能取代自增 BIGINT 或雪花 ID。数据库规模、索引组织方式与 ID 的使用边界依然需要一起考虑。
二、UUID 的 128 位究竟怎么组织?
2.1 UUID 的基本表示
一个 UUID 占用 128 bit = 16 byte,通常显示为 32 个十六进制数字,按照 8-4-4-4-12 分为五组,加上四个连字符后总长为 36 个字符:
1 | |
其二进制值也可以存成连续的 16 字节,而不必保留字符串中的连字符。
UUID 是通用技术名称;GUID(Globally Unique Identifier)常见于 Microsoft 生态,两者在日常工程中通常指兼容的 128 位标识符。但是,Windows COM GUID 的部分二进制序列化方式存在小端字节序历史差异,不能不加区分地直接交换原始字节数组。
2.2 Version 与 Variant 不是一回事
UUID 的两个固定标识字段是:
- Version(版本):整个 UUID 的 bit 48~51,即第 7 个字节(从 0 开始)的高 4 位,显示在第三组十六进制数字的第一个位置。它决定 UUID 的生成算法。
- Variant(变体):RFC 9562 格式中的 bit 64~65,即第 9 个字节的高 2 位,固定为
10。它决定其余位应按哪类 UUID 布局解释。
例如:
1 | |
不要把 Variant 误认为 Version。 同样是符合 RFC 9562 的 UUID,v1~v8 都使用 10 这一变体;差异体现在四位 Version 以及剩余字段的含义上。
| Variant 的前导位 | 第四组首个十六进制字符范围 | 含义 |
|---|---|---|
0xxx |
0~7 |
兼容历史 NCS 格式 |
10xx |
8~b |
RFC 4122 / RFC 9562 格式,本文的主要对象 |
110x |
c~d |
保留给 Microsoft 历史兼容格式 |
111x |
e~f |
保留供将来定义 |
上面的 Variant 有不同长度的前缀,不能简单理解为始终存在一个独立的 4 位 Variant 字段。对于本文讨论的 RFC 格式,其固定高两位是 10。
2.3 早期六字段布局,不能直接套到所有版本
RFC 4122 的经典 UUIDv1 布局可以写成:
1 | |
历史名称 time_low、time_mid、time_hi_and_version 等来自 v1 的设计。
但对于 v3、v4、v5,这些位置根本不存放时间;对于 v6、v7、v8,相应字段的名字和语义也发生了变化。 因此阅读新版标准时,应根据各版本对应的位布局理解数据,而不是把 v1 的六字段解释强加给所有 UUID。
2.4 两种时间纪元
| 时间体系 | 使用版本 | 起点 | 时间单位 | 时间位数 |
|---|---|---|---|---|
| Gregorian Epoch | v1、v6;v2 继承部分字段 | 1582-10-15 00:00:00 UTC | 100 纳秒 tick | 60 位(v2 部分被覆盖) |
| Unix Epoch | v7 | 1970-01-01 00:00:00 UTC | 毫秒 | 48 位 |
v1/v6 的时间计数大致按下式构造:
1 | |
其中常量表示两个纪元之间相差的 100 纳秒计数。格式具有 100 纳秒刻度,不代表操作系统时钟真的能达到 100 纳秒精度;生成器可以借助计数器处理同一时钟刻度中的多次生成。
三、UUID v1~v8:总览比较
| 版本 | 核心材料 | 主要数据字段 | 是否确定性 | 时间可排序性 | 典型使用场景 | 新项目建议 |
|---|---|---|---|---|---|---|
| v1 | Gregorian 时间 + 时钟序列 + 节点标识 | 时间 60 位、Clock Sequence 14 位、Node 48 位 | 否 | 时间字段顺序不理想 | 历史 RPC、兼容系统 | 一般不推荐 |
| v2 | v1 派生 + 本地身份 ID、Domain | Local ID 32 位、Domain 8 位,保留部分时间与节点字段 | 否 | 不适合通用时间排序 | DCE Security / POSIX 身份映射 | 专用场景 |
| v3 | Namespace + Name → MD5 | MD5 摘要有效位共 122 位 | 是 | 否 | 兼容历史名称映射 | 优先替换为 v5(兼容除外) |
| v4 | 加密安全随机数 | 随机位 122 位 | 否 | 否 | 普通随机 ID、跨系统对象 ID | 推荐 |
| v5 | Namespace + Name → SHA-1 | SHA-1 摘要截取并覆盖控制位,122 位有效位 | 是 | 否 | DNS、资源名称、可复现标识 | 推荐 |
| v6 | v1 时间字段高位前置 | 时间 60 位、Clock Sequence 14 位、Node 48 位 | 否 | 跨时间刻度基本有序 | v1 演进、兼容型数据库系统 | 特定场景 |
| v7 | Unix 毫秒时间 + 随机值/可选计数器 | 时间 48 位、剩余 74 位 | 否 | 按毫秒时间有序 | 分布式数据库主键、事件 ID | 推荐 |
| v8 | 自定义算法 | 自定义 122 位 | 由实现决定 | 由实现决定 | 厂商私有协议、SHA-256 名称型 ID | 充分约束后使用 |
表中的“有效位”均指扣除 4 位 Version 和 2 位 Variant 之后的 122 位。名称型 UUID 的有效位来自摘要,不代表拥有与 122 位均匀随机数完全相同的对抗碰撞性质。
四、UUIDv1:时间、时钟序列与节点 ID
4.1 位结构
1 | |
| 字段 | 位数 | 作用 |
|---|---|---|
time_low |
32 | 60 位时间戳的最低 32 位 |
time_mid |
16 | 时间戳中间 16 位 |
time_high |
12 | 时间戳最高 12 位 |
version |
4 | 固定 0001 |
variant |
2 | 固定 10 |
clock_seq |
14 | 处理时钟回拨、节点状态变化等冲突风险 |
node |
48 | 通常来自 MAC,也允许使用随机节点标识 |
4.2 生成规则
- 获取当前时间,将其转换为从 1582-10-15 开始的 100 纳秒单位计数。
- 根据规范维护时钟序列及必要状态,降低时间回拨、节点 ID 变化、重复时间刻度带来的冲突风险。
- 选择 48 位节点标识,可以使用 MAC,也可以依据规范使用随机节点值。
- 把时间戳分别拆成
32 + 16 + 12位,连同时钟序列、节点标识打包。 - 将 Version 固定为 1、Variant 固定为
10。
v1 的唯一性依赖时间、节点标识、时钟序列与实现状态管理的共同约束,不是只要把当前时间和 MAC 拼接起来就绝对不会重复。
4.3 问题与推荐场景
v1 把不断变化最快的时间低 32 位放在最前端,因此按照标准字节序进行比较时,不能在整个时间范围内维持自然的递增顺序。这也是 v6 要调整字段顺序的直接动机。
另外,部分实现会嵌入真实 MAC,泄露生成环境相关信息;通过随机节点标识可以减少这一问题。
适合:读取和兼容历史 v1 数据、必须与旧 RPC 或 UUID 生成器保持一致的系统。
不建议:新设计的通用数据库主键、期望隐藏生成环境的公开标识。
五、UUIDv2:DCE Security 身份型 UUID
UUIDv2 是最容易被误解的版本。RFC 4122 记录其用途是 DCE Security,RFC 9562 继续保留编号,但明确指出 v2 的定义不在该 RFC 的范围内。它不是一个等待未来发明的神秘算法,其详细规则可以追溯到 DCE Security 规范。
5.1 位结构
v2 以 v1 为基础,将 time_low 替换为本地标识符,将 clock_seq_low 替换为 Domain:
1 | |
| 字段 | 位数 | 作用 |
|---|---|---|
local_id |
32 | 指定 Domain 内的本地 ID,例如 POSIX UID、GID |
time_mid |
16 | 保留的 Gregorian 时间中间字段 |
time_high |
12 | 保留的 Gregorian 时间高位字段 |
version |
4 | 固定为 2 |
variant |
2 | 固定 10 |
clock_seq_high |
6 | 时钟序列的剩余部分 |
local_domain |
8 | 标识本地 ID 的类别 |
node |
48 | 节点标识 |
常见的本地 Domain 包括:
| Domain | 含义 | 本地 ID 的例子 |
|---|---|---|
0 |
Person | POSIX 用户 UID |
1 |
Group | POSIX 用户组 GID |
2 |
Organization | 组织或站点定义的标识 |
5.2 生成规则
在 v1 时间型 UUID 的基础上,选择 Local Domain 和对应的 32 位 Local ID,覆盖原先的 time_low,再把 Domain 编码进 clock_seq_low,Version 设置为 2。由于时间戳最低 32 位被覆盖,v2 不再保有 v1 的精细时间信息;按原时间刻度估算,被覆盖的范围相当于约 429.5 秒。
这也解释了为什么 v2 不适合被视作普通的高精度时间型 ID。
适合:确实需要 DCE Security 身份语义的历史/专用系统。
不建议:普通用户主键、权限系统中的真实授权凭据、业务订单 ID。UUIDv2 中含有 UID/GID 信息并不代表持有该 UUID 就经过身份认证。
六、UUIDv3:基于 MD5 的名称型 UUID
6.1 位结构
1 | |
v3 与 v1 看起来一样长,但其余 122 位是从摘要中得到的,不再对应时间、时钟序列和 MAC。
6.2 生成规则
1 | |
同一个 Namespace、同一个规范化后的 Name,会稳定得到同一个 UUID;改动任一输入,结果一般会完全变化。RFC 定义了 DNS、URL、OID、X.500 DN 等命名空间标识。
例如,按标准 DNS Namespace 生成 example.com 的 v3 UUID:
1 | |
适合:必须兼容历史 v3 生成结果的系统、既有 MD5 命名空间数据。
不建议:新项目中的默认名称型算法。MD5 已不适合抵抗有意构造的碰撞,更不能把名称型 UUID 当作签名或认证结果。
七、UUIDv4:最常见的随机型 UUID
7.1 位结构
1 | |
7.2 生成规则
- 从加密安全随机数生成器(CSPRNG)取得随机字节。
- 将版本所在 4 位覆写为
0100。 - 将变体高两位覆写为
10。 - 将最终 128 位按标准格式输出。
UUIDv4 的核心是 122 位随机空间,而非依赖 MAC、机器编号或时间戳。其碰撞概率可用生日悖论近似:
1 | |
其中 n 是生成次数,并假设随机数独立、均匀。如果累计生成 10^12 个 v4 UUID,理论近似碰撞概率约为 9.4 × 10^-14。这个概率非常低,但不是零;随机数质量不佳或错误复用种子会让实际风险远高于该估算。
7.3 推荐场景
v4 适用于应用对象 ID、跨服务关联 ID、普通分布式标识等无需时间顺序的场景,尤其是在生成端之间没有协调机制时。
其主要弱点不是全局冲突风险,而是无序写入数据库索引:当它被用作 InnoDB 聚簇主键时,插入位置散布在索引树的不同叶子页,容易带来额外页读取、页分裂与缓存压力。
推荐:普通随机标识;不以 UUID 为聚簇主键的系统。
审慎使用:超大表、高写入压力的 InnoDB 主键。需要主键插入局部性时,更应评估 v7、BIGINT 或其他有序方案。
v4 采用 CSPRNG 时可能具有不错的不可猜测性,但 UUID 仍只是标识符,不应直接代替认证令牌、访问控制或服务端权限检查。
八、UUIDv5:基于 SHA-1 的名称型 UUID
8.1 位结构
1 | |
v5 与 v3 的字段形状相同,主要区别是哈希算法。
8.2 生成规则
1 | |
仍以 DNS Namespace 下的 example.com 为例,标准 v5 结果是:
1 | |
名称型算法最大的价值是可复现性:不依赖中心发号器,只要参与方采用同一个 Namespace 和完全一致的名称字节序列,就能得到同一个 ID。
实际应用中需要特别约定名称的编码、大小写、Unicode 归一化、URL 格式与是否去除尾部斜杠。即使两个字符串在业务上表示同一对象,只要参与哈希的字节不同,通常就会生成不同 UUID。
8.3 推荐场景
适合:稳定的外部资源映射、DNS/URL 名称映射、跨环境同步时可重算的对象标识、数据导入去重键(前提是业务名称规则稳定且唯一)。
不适合:本来就允许同名的不同对象、需要真正随机且不可推断的标识、对抗性碰撞安全要求严格的摘要身份机制。
RFC 9562 推荐在 v3/v5 之间优先使用 v5;但 SHA-1 也已经不适合新的密码学安全需求。如果必须采用 SHA-256 等更新的哈希算法,不要把 SHA-256 的结果伪装成 v5,而应遵循自定义 UUIDv8 的约定。
九、UUIDv6:给 v1 重新排列时间字段
v6 是为了改善 v1 的数据库局部性而设计的时间型格式,可以理解为“保留 v1 语义,调整时间字段排序”。
9.1 位结构
1 | |
与 v1 放在一起看:
| 版本 | 60 位时间戳在 UUID 中的排列 | 索引特点 |
|---|---|---|
| v1 | 低 32 → 中 16 → 高 12 |
时间低位先变化,跨时间范围排序不自然 |
| v6 | 高 32 → 中 16 → 低 12 |
高位在前,比较结果更接近生成时间 |
9.2 生成规则
- 获取 v1 同源的 60 位 Gregorian 时间戳。
- 按 高 32 位 + 中 16 位 + 低 12 位 的顺序放入 UUID。
- 保留 14 位时钟序列与 48 位节点标识字段的位置。
- 固定 Version 为 6,Variant 为
10。
RFC 9562 建议新生成的 v6 对 Clock Sequence 与 Node 使用伪随机值,但为了兼容已有系统,也允许沿用 v1 的时钟序列/MAC 使用模式。
9.3 推荐场景
适合:既有 v1 时间体系的改造、需要使用 Gregorian 时间结构且希望更好排序的旧系统。
不建议:没有 v1 历史包袱的新业务。RFC 9562 明确提出,不涉及历史 v1 的系统通常应优先采用 v7。
十、UUIDv7:面向现代分布式数据库的时间有序 UUID
v7 是本次标准演进中最值得工程实践关注的版本。它直接采用 Unix 毫秒时间戳,并把该时间戳放在 UUID 的最高 48 位。
10.1 位结构
1 | |
| 字段 | 位数 | 作用 |
|---|---|---|
unix_ts_ms |
48 | Unix Epoch 之后经过的毫秒数,网络字节序 |
version |
4 | 固定 0111 |
rand_a |
12 | 随机值,或单毫秒内的可选子毫秒信息/计数器的一部分 |
variant |
2 | 固定 10 |
rand_b |
62 | 随机值或可选单调计数器的剩余部分 |
可以把 v7 的前 48 位看作可直接读取的时间前缀。按规范的字节序对 UUID 排序时,不同毫秒产生的 UUID 通常自然聚集在一起。
10.2 生成规则:纯随机型与单调增强型
规范允许至少两类实现策略,而不是要求大家使用完全相同的计数器算法。
策略 A:74 位全部用随机数据填充。
1 | |
这个方案简单、分布式节点之间不用协商。但同一毫秒内生成多个 UUID 时,低位是随机的,不能保证同一毫秒内按生成先后严格递增。
策略 B:使用可选的子毫秒精度或计数器增强单调性。
RFC 9562 允许按顺序在剩余 74 位中放置:
- 可选的子毫秒时间分数(最多 12 位);
- 可选的、正确初始化和维护的计数器;
- 仍未使用的位填入随机数据。
生成器可以在同一毫秒内让计数器递增,以满足单个生成器内部的更强排序要求;同时必须考虑时钟回拨、计数器溢出、并发同步和跨进程状态。
| 维度 | 纯随机型 v7 | 单调增强型 v7 |
|---|---|---|
| 跨毫秒排序 | 通常有序 | 通常有序 |
| 同毫秒内严格递增 | 不保证 | 实现正确时,可在单个生成器范围内保证 |
| 分布式节点协调 | 不需要 | 不需要统一发号,但各节点不能天然共享全局次序 |
| 随机性 | 74 位随机 | 部分位用于计数/精度,随机熵减少 |
| 实现复杂度 | 低 | 更高,需要处理回拨与溢出 |
10.3 v7 为什么更适合数据库索引?
B-Tree/B+Tree 按键值组织叶子页。随机 v4 主键可能持续向很多不同的叶子页插入;v7 把时间放在前面,让新数据更可能落到相邻的键范围。
1 | |
这种局部性通常意味着较少的随机页面访问、较好的缓存命中率,以及可能更低的页分裂成本。实际收益取决于写入速率、索引组织、并发热点、页大小与数据库实现,需要基准测试验证。
但必须区分三个容易混淆的结论:
- v7 是时间有序的,不是严格全局单调的。 多节点时钟偏差、同毫秒随机排序、消息延迟都可能让 ID 顺序与真实业务发生顺序不同。
- v7 自带时间,并不意味着可以删掉
created_at。 审计、业务生效时间、延迟写入、回填数据、更新排序、时间修正依旧需要独立时间字段。 - v7 并不保证绝对不碰撞。 规范要求依赖足够质量的随机源或正确设计的计数状态,数据库仍然应设置主键/唯一约束。
10.4 推荐场景
优先考虑:分布式订单主键、支付流水标识、异步事件 ID、日志事件 ID、大量新增写入的数据库表(在业务需要使用 UUID 的前提下)。
不一定适合:要求隐藏大致创建时间的公开 ID、必须有跨节点全序的业务序列号、对主键存储成本极敏感且可使用 BIGINT 的表。
若排序本身就是业务规则,应另外使用可信的时间列、数据库提交顺序、消息偏移量或专门的序列机制,不能仅依赖 v7 的字典序。
十一、UUIDv8:自定义的 122 位空间
v8 是标准预留的实验性/厂商专用 UUID 格式,不是 v7 的功能升级版。
11.1 位结构
1 | |
唯一强制遵守的结构性要求是:Version 必须为 8,Variant 必须为 10。其余 122 位由应用自定义,标准不保证这些自定义内容的唯一性、时间顺序或安全性。
11.2 生成规则与一个设计例子
假设某企业需要按以下逻辑生成标识:
1 | |
这是一个可以设计的 v8 方案方向,并非所有 v8 UUID 都使用 SHA-256,也不代表只写这几行就完成了一个安全协议。生产使用时还需要固定域分离、编码方式、取位规则、版本管理、碰撞处理和测试向量。RFC 9562 附录 B 也提供了 SHA-256 名称型 UUIDv8 的说明性样例。
适合:已有明确位布局协议约束、跨企业约定的自定义 ID、需要比 SHA-1 更新的名称型 UUID 算法。
不建议:只是为了“以后扩展方便”就自创一个 UUID 方案。自定义格式会增加 SDK、数据库和其他团队的互操作成本。
十二、Nil UUID、Max UUID 和常见边界
除了 v1~v8,RFC 9562 还明确了两个特殊值:
| 特殊 UUID | 内容 | 用途 |
|---|---|---|
| Nil UUID | 00000000-0000-0000-0000-000000000000 |
表示空、缺省或不存在的 UUID 值 |
| Max UUID | ffffffff-ffff-ffff-ffff-ffffffffffff |
作为最大值哨兵、范围边界等,取决于应用约定 |
它们不是 UUIDv0 和 UUIDv15。其 Variant 也不属于前述普通 RFC 10 格式,不能拿“第三组是否是 1~8、第四组是否是 8~b”的普通 UUID 检查规则否定它们的存在。
Nil/Max 可以作为约定的哨兵值,但要明确语义。业务中通常更推荐数据库 NULL 表示真正的缺失值,避免哨兵值与有效数据混淆。
十三、Java 实战:v3、v4、v5、v7 怎么生成?
13.1 JDK 自带能力与局限
截至 JDK 25,java.util.UUID 的常用静态工厂方法是:
| Java API | 生成版本 | 注意点 |
|---|---|---|
UUID.randomUUID() |
v4 | 使用加密强度较高的伪随机数生成器 |
UUID.nameUUIDFromBytes(bytes) |
v3 | 基于传入字节的 MD5;不会自动帮你加入 RFC Namespace |
UUID.fromString(text) |
解析 | 只解析表示形式,不生成新 ID |
uuid.version() |
检测 | 返回 Version,Nil/Max 等特殊值不可按普通生成版本解读 |
uuid.variant() |
检测 | RFC 10 变体在 Java 中返回 2 |
JDK 自带的 UUID.randomUUID() 不会因为项目升级到 JDK 21 或 JDK 25 就自动生成 v7。
13.2 使用 JDK 生成 v3、v4、v5,并演示 v7 位布局
下面是一份可独立编译运行的 Java 示例。v3/v5 显式将标准 Namespace UUID 的 16 字节与 UTF-8 名称拼接;v7 以纯随机方案展示 RFC 9562 的字段填充。
1 | |
前两个确定性 UUID 的输出应为:
1 | |
这个 v7 方法适用于理解位布局与验证算法,不建议把教学代码直接作为高并发生产级 ID 组件。例如,它没有加入单毫秒计数器、回拨保护、持久化状态或吞吐压测。生产环境宜使用经过测试的 UUIDv7 实现,并根据业务选择随机型或单调型生成策略。
13.3 Java 工程中使用 uuid-creator
开源项目 uuid-creator 提供了各版本的生成方法。以下 Maven 版本按本文编写时核对的项目文档固定为 6.1.1:
1 | |
1 | |
这里的 v7 方法在当前库中有自己的计数器策略,可按项目文档进一步选用其他单调生成方法。需要注意,“对生成器内部有序”不意味着跨机器严格排序。
十四、数据库实战:MySQL 与 PostgreSQL
14.1 为什么不建议直接把 UUID 主键定义为 CHAR(36)?
UUID 的本体只有 16 字节,但规范文本形式包含 36 个字符。两者相比:
| 列类型 | 表达 UUID 的方式 | 标识本体所需字节数 | 特点 |
|---|---|---|---|
CHAR(36) |
36 字符(包含连字符) | ASCII/UTF-8 文本约 36 字节 | 肉眼可读,但索引键更宽;实际存储还受字符集和行格式影响 |
BINARY(16) |
16 字节原始值 | 16 字节 | MySQL 中通常更适合建立索引,需做显示格式转换 |
PostgreSQL uuid |
原生 UUID 类型 | 16 字节数据 | 数据库原生解析、比较与索引支持 |
BIGINT |
64 位整数 | 8 字节 | 更紧凑,但不是 UUID,分布式唯一性需要额外算法约束 |
尤其在 InnoDB 中,二级索引记录会携带聚簇主键列。较宽的主键不只影响主键索引,还会增加相关二级索引的存储成本。所以是否采用 UUID,不应只看“省掉发号器”这一项收益。
14.2 MySQL:v7 建议按原始顺序存入 BINARY(16)
下面以一个业务订单表为例:
1 | |
应用层生成 UUIDv7 后,将字符串按顺序转换为 16 字节:
1 | |
这里 UUID_TO_BIN(uuid, 0) 的 0 表示不交换字节顺序。
一个很容易踩的坑:UUID_TO_BIN(uuid, 1) 不是给 v7 用的
MySQL 的 UUID_TO_BIN(value, 1) 会交换 UUID 的时间字段,以改善 v1 值在索引中的排列。官方文档明确指出该优化假定输入是 UUIDv1。
| 输入版本 | 建议转换方式 | 说明 |
|---|---|---|
| v1 | UUID_TO_BIN(id, 1) |
通过 MySQL 特定交换方式改善其时间部分的顺序;读取时要对应使用 BIN_TO_UUID(id, 1) |
| v4 | UUID_TO_BIN(id, 0) |
无时间字段可交换,保持标准顺序 |
| v6 | UUID_TO_BIN(id, 0) |
本来就是高位时间在前,不要再按 v1 规则交换 |
| v7 | UUID_TO_BIN(id, 0) |
时间已经位于最前 48 位,交换会破坏预期时间顺序 |
| v3/v5/v8 | UUID_TO_BIN(id, 0) |
通常保留规范字节序,自定义 v8 另按协议约定 |
1 | |
还需要注意,MySQL 的 UUID() 本身产生的是 v1 UUID,不是 v4,更不是 v7。不要因为用了 UUID_TO_BIN() 就误以为数据库函数会自动升级 UUID 版本。
14.3 PostgreSQL 18:原生生成 v7
从 PostgreSQL 18 开始,数据库官方提供了 uuidv7():
1 | |
PostgreSQL 的 uuid 类型本身可以存放多种 UUID 版本;但 uuidv7() 的内建生成能力是 PostgreSQL 18 才引入的,不能照搬到 16/17 并期待内建函数存在。旧版本可以在应用层或经审查的扩展中生成 v7,再写入 uuid 列。
14.4 是否应该用 v7 替换所有自增主键?
| 维度 | BIGINT 自增 | UUIDv4 | UUIDv7 |
|---|---|---|---|
| 主键宽度 | 8 字节 | 16 字节 | 16 字节 |
| 多节点无协调生成 | 通常不具备 | 支持 | 支持 |
| 大致按生成时间排列 | 单实例序列递增,但不直接代表时间 | 不支持 | 支持 |
| B-Tree 插入局部性 | 通常很好 | 通常较差 | 通常较好 |
| 是否暴露大致生成时间 | ID 本身未显式编码时间 | 不暴露 | 暴露 |
| 依赖中心数据库发号 | 常见 | 否 | 否 |
| 典型场景 | 单库/紧凑内部主键 | 普通随机标识 | 分布式可排序 UUID 主键 |
如果所有写入都走同一个 MySQL 实例,自增 BIGINT 的简单、紧凑和成熟仍有价值;如果多服务必须独立生成主键,UUIDv7 是有吸引力的替代方案。两者的取舍必须把网络协调成本、索引成本、主键大小、数据迁移成本和业务暴露要求一起衡量。
十五、工程设计中容易忽略的六个问题
| 误区 | 更准确的理解 |
|---|---|
| “UUID 一定不会重复” | UUID 的唯一性依赖版本算法、节点状态或随机质量;应用仍须维护唯一约束和冲突处理策略 |
| “Version 数字越大就越好” | Version 是不同生成算法的编号;v8 不会天然优于 v7,v7 也不适合所有场景 |
| “UUIDv7 严格全局递增” | 标准只提供时间高位布局;单调性与跨节点有序性取决于实现、时间同步和范围 |
| “UUIDv7 有时间戳,可以删掉创建时间列” | ID 时间是生成时刻线索,无法代替审计、业务创建/提交时间或校正后的时间字段 |
| “名称型 UUID 可以作为摘要签名” | v3/v5 的主要目标是确定性命名,而不是密码学身份认证;对抗性散列场景需单独设计安全方案 |
| “MySQL UUID_TO_BIN 的 swap=1 永远更快” | swap=1 针对 v1 时间字段优化;v6/v7 应按原始网络字节序存放 |
还有一些具体工程边界值得提前确定:
- 时钟与多节点:v7 依赖系统时间。节点时钟明显回拨或时钟不同步时,业务上“后写入”未必对应“更大的 UUID”。
- 容量与索引:UUID 的 128 位不会因为改用
BINARY(16)而变成 64 位;二级索引依旧受到较宽主键影响。 - 安全与隐私:v1/v2/v6 的 Node 可能携带设备信息,v7 公开了生成时间;v4、v5 的用途也不等于安全令牌。
- 跨语言序列化:Java、Go、Python、SQL、Windows GUID 等生态之间交换 16 字节时,要统一规范字节序,不能只保证文本看起来一致。
- 业务去重:订单号、请求幂等键、业务自然键与 UUID 主键是不同概念。UUID 本身不能阻止重复下单。
- 版本升级:旧表从 v4 迁到 v7 不一定要重写全部历史 ID,但混合版本的全列排序不再等价于时间排序;迁移前应评估主键与外键约束。
十六、版本选型决策图与学习路线
下图用于快速判断生成方式,而不是声称所有系统都必须使用 UUID。Hexo 主题若未启用 Mermaid 渲染插件,图形代码块会按普通代码展示,下面的对比表仍可独立阅读:
flowchart TD
A[需要生成一个标识符] --> B{必须采用 UUID 吗?}
B -->|否| C[评估 BIGINT / 序列 / Snowflake 等]
B -->|是| D{相同名称是否必须生成相同 ID?}
D -->|是| E{需要自定义哈希或字段吗?}
E -->|否| F[UUIDv5]
E -->|是| G[UUIDv8 + 明确协议]
D -->|否| H{是否需要时间有序/索引局部性?}
H -->|否| I[UUIDv4]
H -->|是| J{需要兼容历史 v1 格式吗?}
J -->|是| K[UUIDv6]
J -->|否| L[UUIDv7]
学习时可以按照下面的顺序建立理解,不必机械按 v1、v2、v3……依次死记:
| 阶段 | 重点 | 应该掌握什么 |
|---|---|---|
| 入门 | UUID 128 位格式、Version、Variant | 会读一个 UUID,知道 8-4-4-4-12 和固定控制位 |
| 基础 | v4 与 v5 | 理解随机唯一性和名称确定性是两种不同需求 |
| 原理 | v1 与 v6 | 理解 Gregorian 时间、Clock Sequence、Node、时间字段重排 |
| 工程重点 | v7 | 理解 48 位时间戳、74 位剩余数据、同毫秒单调性与数据库局部性 |
| 历史兼容 | v2 与 v3 | 了解 DCE 身份场景和 MD5 名称生成的兼容价值 |
| 高级扩展 | v8 | 能设计并评审自定义位布局、碰撞模型和跨语言协议 |
| 落地 | Java + MySQL/PostgreSQL | 正确生成、序列化、存储、索引与压测 |
最后再用一张表记住结构
1 | |
总结
UUID 解决的首先是“不依赖中心发号器,如何生成低冲突概率、可跨系统使用的标识”;不同版本又针对可复现性、时间结构、索引局部性和自定义协议给出了不同答案。
从工程选择来看,最常用的并不是八种版本各取一点,而是围绕三个问题做取舍:是否需要随机标识、是否需要名称确定性、是否需要时间有序。 对应的默认选项分别是 v4、v5 和 v7。v1/v6 解决历史时间型格式及兼容问题,v2 是 DCE 专用身份格式,v3 留给老系统,v8 则为真正有协议设计需求的场景保留空间。
对于新的 Java 分布式服务,如果需要使用 UUID 作为 MySQL 主键,我会优先评估 UUIDv7 + BINARY(16) 标准字节序存储 + 唯一约束 + 独立创建时间列。它不一定比 BIGINT 更轻,但相较无序 v4 主键,通常是更符合数据库写入特性的 UUID 选择。
参考资料
本次提供的四篇资料
- Nicole Tietz-Sokolskaya, TIL: 8 versions of UUID and when to use them. 原文
- IETF, RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace(2005;已由 RFC 9562 取代). RFC Editor
- 博客园 -云-,深度解读 UUID:结构、原理以及生成机制. 原文
- CSDN,UUID 的 8 个版本以及何时使用它们. 原文
补充核对的规范与官方文档
- IETF, RFC 9562: Universally Unique IDentifiers (UUIDs)(2024,现行标准). RFC 原文
- The Open Group, DCE 1.1 / DCE Security(UUIDv2 的历史规范背景). DCE UUID 规范入口
- Oracle, MySQL Reference Manual —
UUID(),UUID_TO_BIN(),BIN_TO_UUID(). 官方手册 - PostgreSQL, UUID Functions (PostgreSQL 18). 官方手册
- Oracle, Java SE 25 — java.util.UUID. JDK API
- Fabio Lima, uuid-creator. 项目仓库及示例