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 :标准化、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 后索引效率会自动提升(因为新数据变成了顺序插入)。
微信赞赏
支付宝扫码领红包





