UUIDs (v1–v8) — 完整指南

TL;DR(快读要点)

  • UUID 是 128 位的通用标识符,长期由 RFC 4122 定义;最新版规范是 RFC 9562,它对早期规范做了更新并正式说明了新版格式(v6/v7/v8 等)。(datatracker.ietf.org)

  • 各版本核心思路:

    • v1:时间 + 节点(通常是 MAC)+ 计数 → 有序但可能泄露节点信息。(datatracker.ietf.org)
    • v3/v5:基于名字(命名空间 + 名称)分别用 MD5 / SHA-1 → 确定性(same input → same UUID)。
    • v4:随机 → 最常用,最高熵但无序(随机)。(Docs.rs)
    • v6/v7:现代时间排序 UUID(为 DB 主键优化);v7 用 unix-ms 时间 + 随机/计数,适合分布式可排序键。(IETF)
    • v8:自定义格式(用户可定义剩余位)。(datatracker.ietf.org)
  • Rust 的 uuid crate 使用 feature flags 启用各版本/依赖(例如 v4 -> rngv1/v6 -> atomic 等),参见 crate features。示例演示如何在项目中只启用你需要的版本。(Docs.rs)


1. 背景 / 标准来源(必须知道的几份规范)

  • RFC 4122 (2005):最初被广泛引用的 UUID 规范,定义了 v1/v3/v4/v5 等。(datatracker.ietf.org)
  • RFC 9562 (更新,2024):更新/整合了 UUID 规范(逐步替代 RFC4122 的若干内容),并正式化了 v6/v7/v8 等“现代”格式。若你需要权威定义和示例,请以 RFC 9562 为准。(datatracker.ietf.org)
  • IETF 草案 / 设计动机(如 Peabody & Davis 的“New UUID Formats”):解释为什么需要 v6/v7(数据库索引、排序、分布式生成的现实权衡)。阅读草案能更好理解设计取舍。(IETF)

2. 从理论上看:各版本是什么、适合哪些场景(逐版说明)

下面每一节包含:原理、优缺点、何时用。

v1 — 时间 + 节点(MAC)+ 序列(经典时间 UUID)

  • 原理:以 100-ns tick(RFC 的时间基准)作为时间字段 + 48-bit 节点 ID(通常是 MAC 地址) + clock sequence 保证同一时间戳下不重复。(datatracker.ietf.org)
  • 优点:生成时间可排序(大致按时间),生成器无需中心化。
  • 缺点会暴露节点 ID(MAC),存在隐私风险;实现要注意避免时钟回拨导致重复(需要 clock-sequence 机制)。
  • 场景:当你需要“时间可推断且全局唯一”的 ID 且能接受节点标识暴露(或你以其他方式设置 node),可以考虑。
  • Rust(uuid crate):crate 提供 Uuid::new_v1(...) / Uuid::now_v1(...)(注意需要启用 v1 feature 与相关依赖)。(Docs.rs)

v2 — DCE 安全(很少用)

  • DCE 特殊用法(与 POSIX UID/GID 相关),在一般应用中较少见;uuid crate 常见默认不强制启用。RFC 中描述历史兼容性(不展开)。

v3 / v5 — 名称哈希(确定性)

  • 原理namespace UUID + name 写入哈希:

    • v3 用 MD5;v5 用 SHA-1(更强)。
  • 优点:确定性(相同 namespace+name → 相同 UUID),便于“根据名字生成稳定 ID”。

  • 缺点:非随机(可被穷举/逆向)→ 不适合作为密钥/需要抗猜测的场景。

  • 场景:为固定资源(如 URL、DNS 名称、类型名称)生成稳定标识符。

  • RustUuid::new_v3(&NAMESPACE_DNS, b"example.com") / Uuid::new_v5(...)(需启用 v3 / v5 feature)。(Docs.rs)

v4 — 随机 UUID(最常用)

  • 原理:128 位随机数据(规范要求一定的位设置以表明版本/variant)。
  • 优点:简单、冲突概率极低;不包含时间或节点信息。
  • 缺点:排序性能差(随机)。
  • 场景:短生命周期的 token、session id、一般分配的对象 id。
  • RustUuid::new_v4()(使用操作系统 RNG,如 getrandom),需启用 v4 feature。(Docs.rs)

v6 — 基于 v1 的“排序优化”时间 UUID

  • 原理:和 v1 一样用时间 & node,但把时间字段调整为更靠前,从而字典序/字节序上更可排序(更利于索引)。
  • 优点:兼顾时间信息与排序友好性,比原始 v1 更适合做 DB 主键。(datatracker.ietf.org)
  • 缺点:仍然使用 node(若使用 MAC,仍有泄露问题)。uuid crate 支持 now_v6 / new_v6。(Docs.rs)

v7 — Unix-ms 时间 + 随机(现代推荐用于数据库主键)

  • 原理:前 48 位放 Unix epoch 毫秒(big-endian),接着版本位、计数、剩余随机位(规范给出位划分与计数策略)。设计目标是:高效排序 + 分布式生成 + 抗冲突。(IETF)
  • 优点:天然按时间排序(对索引友好)、不依赖 MAC、较易在分布式环境下安全生成(用随机 + 局部计数)。非常适合作为数据库主键。(datatracker.ietf.org)
  • 缺点:包含时间信息(如果时间私密,也需注意)。在极端 high-throughput 场景需配置计数/上下文以保证同毫秒内有序。uuid crate 有 Uuid::new_v7 / Uuid::now_v7ContextV7 辅助。(Docs.rs)

v8 — 自定义(vendor / 应用自定义)

  • 原理:版本位与 variant 位占位后,剩余比特由用户/厂商自定义编码规则。
  • 优点:非常灵活,可把业务自有信息或编码放入 UUID(但必须遵守 version/variant 位)。(datatracker.ietf.org)
  • 缺点:兼容性/可移植性可能受限(不同系统可能无法理解自定义含义)。Rust 的 uuid crate 提供 Uuid::new_v8(buf: [u8;16]) 来创建这类 UUID。(Docs.rs)

3. uuid crate(Rust)里的 feature 映射 — 一目了然

uuid crate(1.18.1)把版本功能做成 Cargo feature,以避免不必要的依赖。下面是关键映射(来自 docs.rs features 列表):

  • v1 → 会开启 atomic(用于计数/上下文)。
  • v3 → 会开启 md5(依赖 md-5 crate)。
  • v4 → 会开启 rnggetrandom / OS RNG)。
  • v5 → 会开启 sha1(依赖 sha1 库)。
  • v6 → 会开启 atomic
  • v7 → 会开启 rng
  • v8 → 该 flag 本身不再额外启用依赖(只是暴露 API)。
  • 另外 zerocopy 是独立 feature,允许零拷贝转换。(Docs.rs)

因此:在 Cargo.toml 里只启用你用到的版本可以减少二进制/依赖体积。


4. Rust 示例(按版本给出最小、实际可运行示例)

所有示例假设使用 uuid = "1.18.1"(或 latest),并在 Cargo.toml 中按需启用对应 feature。下面给出可复制最小示例与推荐的 feature 行。


Cargo.toml(最小:只启 v4 与 v7 的例子)

[package]
name = "uuid_demo"
version = "0.1.0"
edition = "2021"

[dependencies]
uuid = { version = "1.18.1", features = ["v4", "v7"] }

v4 用于随机 ID;v7 用于时间可排序 ID。如需 v1/v6/v3/v5/v8,只把对应 feature 加进来即可(参见上文映射)。(Docs.rs)


生成 v4(随机)

use uuid::Uuid;

fn main() {
    let u = Uuid::new_v4(); // 需启用 `v4` feature
    println!("v4: {}", u);
}

说明Uuid::new_v4() 使用操作系统 RNG(getrandom),保证高质量随机来源。(Docs.rs)


生成 v1(时间 + node) — 简单示例

# Cargo.toml
uuid = { version = "1.18.1", features = ["v1", "rng", "std"] }
use uuid::Uuid;

fn main() {
    // 示例 node id(生产环境不要直接用真实 MAC;可随机化或用保密的 node)
    let node: [u8; 6] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
    let u = Uuid::now_v1(&node); // 需要 v1 + std + rng features
    println!("v1: {}", u);
}

注意 / 建议:v1 默认会把 node(常见为 MAC)编码进 UUID;若不想泄露真实 MAC,应使用随机/约定的 node 值或对 node 做哈希处理。(Docs.rs)


生成 v3 / v5(名称哈希)

# Cargo.toml
uuid = { version = "1.18.1", features = ["v3", "v5"] }
use uuid::{Uuid, NAMESPACE_DNS};

fn main() {
    let name = b"example.com";
    let u3 = Uuid::new_v3(&NAMESPACE_DNS, name); // MD5-based
    let u5 = Uuid::new_v5(&NAMESPACE_DNS, name); // SHA-1-based
    println!("v3: {}", u3);
    println!("v5: {}", u5);
}

生成 v6(时间排序化的 v1)

uuid = { version = "1.18.1", features = ["v6", "rng", "std"] }
use uuid::Uuid;

fn main() {
    let node = [1u8,2,3,4,5,6];
    let u6 = Uuid::now_v6(&node); // v6, std + rng
    println!("v6: {}", u6);
}

用途:可直接用于需要排序的 DB 主键(仍需注意 node 的隐私问题)。(Docs.rs)


生成 v7(Unix ms + random) — 推荐用于 DB 主键

uuid = { version = "1.18.1", features = ["v7"] }
use uuid::Uuid;

fn main() {
    // 方便用法(使用系统时间),需要默认启用 std(通常为默认)
    let u7 = Uuid::now_v7(); // 需启用 `v7` feature
    println!("v7: {}", u7);
}

高级用法:如果你批量生成并希望在同一毫秒内保持可排序性,可用 ContextV7Timestamp::from_unix(&context, ...) 来创建有序的 counter。示例与文档在 crate 中有清楚说明。(Docs.rs)


生成 v8(用户自定义 bytes)

uuid = { version = "1.18.1", features = ["v8"] }
use uuid::Uuid;

fn main() {
    let mut buf = [0u8; 16];
    // 用业务自己的字节布局;UUID crate 会注入 version & variant 位
    buf[0..16].copy_from_slice(&[/* 16 bytes of your payload */]);
    let u8 = Uuid::new_v8(buf);
    println!("v8: {}", u8);
}

说明:v8 允许你把业务编码或特别语义放进剩余比特,但记住要遵守 version/variant 的位要求。(Docs.rs)


5. 实战建议:如何选版本(常见场景)

  • 数据库主键(排序友好、写放大小):推荐 v7v6(v7 更现代,不依赖 node;v6 也行但若不想暴露 node 要小心)。这些版本使索引写入更聚簇、避免 v4 的随机插入带来的索引碎片化。(IETF)
  • 随机 token / 不需要可排序v4(简单、抗猜测)。(Docs.rs)
  • 需要稳定、可重现的 ID(同输入同 ID)v3 / v5(选择 v5 优于 v3,因为 SHA-1 更强)。
  • 兼容旧系统或需要时间细节v1(但注意 node 的隐私/泄露问题)。(datatracker.ietf.org)
  • 自定义业务编码v8(协议/厂商自定义)。(datatracker.ietf.org)

6. 常见陷阱与安全注意

  • v1 会暴露 node(MAC):默认 node 字段可能是物理网卡 MAC,从隐私/泄露角度不建议在公网/日志里直接使用真实 MAC。如需用 v1/v6,请把 node 换成随机或私有标识。(datatracker.ietf.org)
  • RNG 质量v4v7 的随机位依赖系统 RNG(getrandom / rand);确保运行环境能提供高质量熵(container / early-boot 环境要小心)。uuid crate 文档说明 new_v4 使用 getrandom。(Docs.rs)
  • 时钟回拨:时间型 UUID(v1/v6/v7)在系统时钟回拨时需处理(规范里有 clock-sequence / counters 的建议)。生产实现应采用 crate-provided Context/ClockSequence 或自己实现合适的策略。(datatracker.ietf.org)
  • 排序的假象:即使 v7 提供字典序可排序,跨多实例/多机的严格单调性仍取决于时钟同步与上下文设计 —— 为了完全保证顺序,可能需要更复杂的逻辑(例如全局协调或 hybrid logical clocks)。草案和 RFC 讨论了这些权衡。(IETF)

7. 一个小 demo:比较 v4(随机)与 v7(时间)排序行为

下面为演示性代码:会生成若干 v4 与 v7,然后排序并打印,以便直观看到差别。Cargo.toml 中启用 v4v7

// Cargo.toml
// uuid = { version = "1.18.1", features = ["v4", "v7"] }

use uuid::Uuid;

fn main() {
    // 生成一些 v4(随机)
    let mut rands: Vec<Uuid> = (0..10).map(|_| Uuid::new_v4()).collect();
    // 生成一些 v7(时间排序)
    let mut times: Vec<Uuid> = (0..10).map(|_| Uuid::now_v7()).collect();

    // 打印原序
    println!("v4 original:");
    for u in &rands { println!("{}", u); }

    println!("\nv7 original:");
    for u in &times { println!("{}", u); }

    // 排序并显示(Uuid 实现了 Ord,可直接排序)
    rands.sort();
    times.sort();

    println!("\nv4 sorted:");
    for u in &rands { println!("{}", u); }

    println!("\nv7 sorted:");
    for u in &times { println!("{}", u); }
}

预期观察

  • v4 排序看起来“随机”(排序后的顺序对时间并无明显含义);
  • v7 排序将按时间先后具有良好的单调性(尤其是同一进程生成时 now_v7 保证单进程顺序)。要注意:跨机器的严格时间单调仍取决于时钟同步与计数策略。(Docs.rs)

8. 迁移策略(例如从 v4 到 v7)

  • 平滑迁移:在系统中同时支持 v4 与 v7(字段或标记区分),在新写入使用 v7,旧数据逐步回填/后台迁移。
  • 索引迁移:为避免在线重建索引带来的影响,可在 DB 中增加新列存放 v7,然后在合适窗口切换主键或使用二级索引。
  • 兼容性:存储格式应统一为 16 字节(UUID 的 canonical bytes),这样仅改变生成方式即可而不会改变存储类型。
  • 审计与日志:迁移前审计哪些系统/脚本依赖于 UUID 的时间或随机特性(比如 log 里从 v1 解析出时间、或从 v8 解析自定义字段等)。

9. 参考(权威文档与资料)

  • RFC 9562 — Universally Unique IDentifiers (UUIDs)(新版规范,合并/更新早期工作)。(datatracker.ietf.org)
  • RFC 4122 — A Universally Unique IDentifier (UUID) URN Namespace(经典,历史背景)。(datatracker.ietf.org)
  • IETF draft: New UUID Formats(Peabody & Davis,关于 v6/v7 的设计动机)。(IETF)
  • uuid crate docs (1.18.1) — feature 映射、API 文档与例子(new_v4now_v1now_v6now_v7 等)。(Docs.rs)

结束语

简短建议:如果要把 UUID 当作数据库主键/排序键,优先考虑 v7;如果要最大限度抗猜测、且不关心顺序,选择 v4;若要稳定可复现 ID(同输入同 ID),选择 v5(或 v3)