UUID.randomUUID() 大概是 Java 里被调用得最多的工具方法之一。从数据库主键到分布式 traceId,到处都有它的身影。用了这么多年,大家也习惯了它”能用就行”的定位。

但 UUIDv4 有一个老问题一直没解决:它是完全随机的,没有时间信息,塞进 B+ Tree 索引里会造成大量页分裂 。这个问题在高写入场景下是实打实的性能杀手。

Java 26 终于把这个坑填上了——原生支持 UUIDv7。

图片

UUIDv4 的三宗罪

1. 索引性能差

UUIDv4 是 122 位随机数,新插入的值在已有数据中的位置完全随机。对于 B+ Tree 索引来说,这意味着几乎每次插入都要加载不同的页面——随机写 。在 MySQL InnoDB 里,这个问题尤其严重,因为聚簇索引(主键索引)的物理存储顺序就是按主键排的,随机主键 = 随机 IO。

实测数据:1000 万条记录的表,UUIDv4 主键比自增 ID 主键的写入速度慢 3-5 倍 ,索引体积大 30-50% 。

2. 不可排序

没有时间信息,无法按生成顺序排序。想知道”哪条记录先创建的”?对不起,UUIDv4 帮不了你。很多团队不得不额外加一个 created_at 字段来弥补。

3. 信息密度低

128 位全给了随机数,除了保证唯一性之外,不携带任何业务信息。同样 128 位的空间,可以做更多事。

UUIDv7:时间有序的新标准

2024 年 5 月,RFC 9562 正式发布,定义了 UUIDv7 规范。核心变化:

UUIDv7 结构(128 bits):
┌─────────────────────────────────────┐
│ 48-bit Unix Timestamp (毫秒精度)     │  ← 前 48 位是时间戳
│ 4-bit Version (0111)                │
│ 12-bit Random                       │
│ 2-bit Variant (10)                  │
│ 62-bit Random                       │  ← 剩余 74 位随机数
└─────────────────────────────────────┘

关键改进 :

  • 时间有序 :前 48 位是毫秒级 Unix 时间戳,天然按生成时间排序
  • 索引友好 :新记录总是追加在索引末尾,从随机写变成顺序写
  • 保持唯一性 :74 位随机数足以保证同一毫秒内不重复

Java 26 终于原生支持了

// Java 26 新增 API
UUID uuid = UUID.nameUUIDv7();

// 提取时间戳
Instant timestamp = uuid.getTimestamp();

// 一行代码搞定,不再需要第三方库

之前想用 UUIDv7,要么引 com.fasterxml.uuid(Java UUID Generator),要么自己手撸。Java 26 终于把它变成了标准 API。

注意 :UUID.randomUUID() 仍然生成 v4,新方法是 UUID.nameUUIDv7(),别搞混了。

单调递增的陷阱

UUIDv7 是”时间有序”,但同一毫秒内生成的多个 UUID 并不保证严格递增 ——毫秒内的顺序由随机数部分决定。

在高并发场景下(比如同一毫秒内生成上百个 UUID),这可能导致局部乱序。如果你的场景对严格递增有要求(比如用作消息队列的 offset),需要在应用层加一个单调递增的计数器:

// 单调递增的 UUIDv7 生成器(伪代码)
publicclass MonotonicUUIDv7 {
    privatelong lastTimestamp = 0;
    privatelong counter = 0;
    
    public synchronized UUID next() {
        long now = System.currentTimeMillis();
        if (now == lastTimestamp) {
            counter++;
        } else {
            lastTimestamp = now;
            counter = 0;
        }
        // 用 counter 替换随机数的高位部分
        return buildUUIDv7(now, counter);
    }
}

大多数场景不需要这么做。 数据库主键、分布式 ID 这些常见用途,UUIDv7 默认的”毫秒级有序 + 毫秒内随机”已经足够好。

UUIDv7 vs Snowflake vs ULID

对比维度
UUIDv7
Snowflake
ULID
长度
128 bit (36 字符)
64 bit (19 位数字)
128 bit (26 字符)
时间精度
毫秒
毫秒
毫秒
有序性
毫秒级有序
严格递增
毫秒级有序
标准化
RFC 9562
无(各家实现不同)
社区规范
依赖
需要 Worker ID 分配
需要第三方库
JDK 原生
Java 26+
适合场景
分布式系统通用 ID
高并发严格递增场景
需要紧凑编码的场景

选型建议 :

  • 默认选 UUIDv7 :标准化、JDK 原生、够用
  • 需要严格递增 + 紧凑 :Snowflake(但要自己搞 Worker ID 分配)
  • 需要 URL 友好的短 ID :ULID(26 字符 Crockford Base32)

迁移指南:从 v4 到 v7

新项目 :直接用 UUID.nameUUIDv7(),没有理由再用 v4。

老项目迁移 :

// 1. 新记录用 v7
UUID newId = UUID.nameUUIDv7();

// 2. 判断已有 UUID 的版本
UUID existingId = UUID.fromString("...");
if (existingId.version() == 4) {
    // 旧数据,v4 格式
} else if (existingId.version() == 7) {
    // 新数据,v7 格式
}

v4 和 v7 可以在同一张表里共存——它们都是标准 UUID 格式,只是版本号不同。迁移不需要一步到位,新数据用 v7、老数据保持 v4 就行。

数据库层面 :如果主键是 CHAR(36) 或 BINARY(16),不需要改表结构。如果之前建了索引,切换到 v7 后索引效率会自动提升(因为新数据变成了顺序插入)。

扫码领红包

微信赞赏支付宝扫码领红包

发表回复

后才能评论