泪目!UUID用了这么多年,Java 26终于把最大的坑填上了
这是一个或许对你有用的社群 一对一交流/面试小册/简历优化/求职解惑欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料《项目实战视频》从书中学往事上“练”《互联网高频面试题》面朝简历学习春暖花开《架构 x 系统设计》摧枯拉朽掌控面试高频场景题《精进 Java 学习指南》系统学习互联网主流技术栈《必读 Java 源码专栏》知其然知其所以然这是一个或许对你有用的开源项目国产Star破10w的开源项目前端包括管理后台、微信小程序后端支持单体、微服务架构RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRM、AI大模型、IoT物联网等功能多模块https://gitee.com/zhijiantianya/ruoyi-vue-pro微服务https://gitee.com/zhijiantianya/yudao-cloud视频教程https://doc.iocoder.cn【国内首批】支持 JDK17/21SpringBoot3、JDK8/11Spring Boot2双版本UUIDv4 的三宗罪UUIDv7时间有序的新标准Java 26 终于原生支持了单调递增的陷阱UUIDv7 vs Snowflake vs ULID迁移指南从 v4 到 v7UUID.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 位的空间可以做更多事。基于 Spring Boot MyBatis Plus Vue Element 实现的后台管理系统 用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能项目地址https://github.com/YunaiV/ruoyi-vue-pro视频教程https://doc.iocoder.cn/video/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 位随机数足以保证同一毫秒内不重复基于 Spring Cloud Alibaba Gateway Nacos RocketMQ Vue Element 实现的后台管理系统 用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能项目地址https://github.com/YunaiV/yudao-cloud视频教程https://doc.iocoder.cn/video/Java 26 终于原生支持了// Java 26 新增 API UUID uuid UUID.nameUUIDv7(); // 提取时间戳 Instant timestamp uuid.getTimestamp(); // 一行代码搞定不再需要第三方库之前想用 UUIDv7要么引com.fasterxml.uuidJava 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对比维度UUIDv7SnowflakeULID长度128 bit (36 字符)64 bit (19 位数字)128 bit (26 字符)时间精度毫秒毫秒毫秒有序性毫秒级有序严格递增毫秒级有序标准化RFC 9562无各家实现不同社区规范依赖无需要 Worker ID 分配需要第三方库JDK 原生Java 26❌❌适合场景分布式系统通用 ID高并发严格递增场景需要紧凑编码的场景选型建议默认选 UUIDv7标准化、JDK 原生、够用需要严格递增 紧凑Snowflake但要自己搞 Worker ID 分配需要 URL 友好的短 IDULID26 字符 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 后索引效率会自动提升因为新数据变成了顺序插入。欢迎加入我的知识星球全面提升技术能力。 加入方式“长按”或“扫描”下方二维码噢星球的内容包括项目实战、面试招聘、源码解析、学习路线。文章有帮助的话在看转发吧。 谢谢支持哟 (*^__^*