dbaplus社群 10小时前
P7领导建议:可以用UUIDv7作为数据库主键
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

文章探讨了使用UUIDv7作为数据库主键的可行性,尤其是在分布式系统中。UUIDv7通过将时间戳嵌入UUID中,解决了传统UUID(如v4)的无序性问题,从而提升了写入性能和范围查询效率。文章详细介绍了UUIDv7的结构设计、优势,以及如何在实际项目中使用,并解答了关于UUIDv7重复和时钟回拨等常见问题,为开发者提供了关于数据库主键选择的全新视角。

⏱️ **传统UUID的局限性:** 传统的UUID(如v4)因其完全随机性,导致数据库索引无序,写入性能下降,范围查询效率低,并且占用存储空间大。

💡 **UUIDv7的核心创新:** UUIDv7通过将时间戳嵌入最高有效位,实现了全局单调递增。其结构包括48位Unix毫秒时间戳和74位随机位,确保了ID的时间有序性和分布式唯一性。

🚀 **UUIDv7的性能优势:** UUIDv7的时间有序性优化了B+树索引,新生成的UUID被追加到索引尾部,避免了索引分裂,提高了写入性能。时间有序性也加速了范围查询,使WHERE条件转换为时间戳范围过滤,从而降低了扫描范围。

🛠️ **UUIDv7的实战应用:** 文章提供了生成和使用UUIDv7的代码示例,包括生成UUIDv7、转换为数据库存储格式以及从数据库读取转换。示例展示了如何在数据库中将UUIDv7用作主键,并进行插入和查询操作。

⚠️ **UUIDv7的常见问题与解答:** 文章解答了UUIDv7的重复概率问题,指出其重复概率极低,可以忽略不计。同时,文章解释了时钟回拨的概念及其对UUIDv7的影响,并提出了预防和容错措施,如使用冗余时钟源、监控时钟漂移、时间戳延续和随机数扩容等。

田螺 2025-06-23 07:16 广东

谁说不能用UUID做主键?!

我们提到分布式主键ID的时候,可能都会想到UUID,比如在设计数据库主键的时候。但是可能最终都不会考虑它。但是呢,最近领导却建议说,可以考虑它作为数据库主键了,因为UUIDv7的出现~~


一、传统 UUID 作为主键的缺点


传统 UUID(尤其是 v4)的 完全随机性 是其作为数据库主键的“原罪”:


无序性 (最主要问题):


    数据库索引(尤其是 B+Tree)依赖主键顺序插入新记录效率最高。

    UUID 随机生成,插入位置不确定,导致索引树频繁分裂和重组,大幅降低写入性能。

    破坏聚簇索引(如 InnoDB)的物理存储顺序,增加磁盘 I/O。

    范围查询和排序效率低下,性能低下。


存储空间大:


占用存储空间是自增整数(如 64位 BigInt)的 2倍。

导致索引更大,占用更多内存/磁盘,缓存效率降低,查询变慢


二、UUIDv7 的核心突破:时间有序性架构设计


UUIDv7 的革新性在于将 时间戳嵌入最高有效位(Most Significant Bits),实现了全局单调递增。其 128 位结构如下:


     0                   1                   2                   3

     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

    ┌─────────────────────┬─────┬─────┬─────────────────────────────┐

    │   Unix毫秒时间戳    │ Ver │Var  │          随机位            │

    │      (48位)         │(4) │(2) │          (74位)            │

    └─────────────────────┴─────┴─────┴─────────────────────────────┘


    设计关键点解析:


      高精度时间前缀(48位):精确到毫秒的 Unix 时间戳,确保 ID 严格按时间递增(需 NTP 时钟同步)。

      尾部随机位(74位):保证分布式唯一性,避免 v1 的 MAC 地址泄露风险。


    有序性如何解决性能问题?


      B+树索引优化


    新生成的 UUIDv7 总是大于之前的值,因此被追加到索引尾部,避免中间节点分裂。


      缓冲池友好顺序写入使新记录集中在少数数据页。当页写满时,数据库只需分配新页追加,减少旧页淘汰与磁盘I/O。


      范围查询加速时间有序性使 WHERE id > '2025-06-01' 可转化为 时间戳范围过滤,大幅降低扫描范围


    三、UUIDv7 和其他版本的横向对比



    四、项目实战:如何使用 UUIDv7?


      生成 UUIDv7


      import com.github.f4b6a3.uuid.UuidCreator;


      public class UuidUtils {

          public static UUID generateUuidV7() {

              return UuidCreator.getTimeOrdered(); // 生成 UUIDv7

          }

          


           // 转为数据库存储格式

          public static byte[] toBytes(UUID uuid) {

              ByteBuffer bb = ByteBuffer.wrap(new byte[16]);

              bb.putLong(uuid.getMostSignificantBits());

              bb.putLong(uuid.getLeastSignificantBits());

              return bb.array();

          }

          


          // 从数据库读取转换

          public static UUID fromBytes(byte[] bytes) {

              ByteBuffer bb = ByteBuffer.wrap(bytes);

              return new UUID(bb.getLong(), bb.getLong());

          }

          


      }


      // 使用示例

      UUID id = UuidService.generateUuidV7();


        数据库作为主键


        CREATE TABLE users (

            id BINARY(16PRIMARY KEY, -- 二进制存储 UUID

            name VARCHAR(50NOT NULL,

            email VARCHAR(100)

        );


          插入数据库和查询


          // 插入数据

          UUID userId = UuidUtils.generateUuidV7();

          String sql = "INSERT INTO users (id, name) VALUES (?, ?)";


          try (PreparedStatement ps = conn.prepareStatement(sql)) {

              ps.setBytes(1, UuidUtils.toBytes(userId)); 

              ps.setString(2"John Doe");

              ps.executeUpdate();

          }


          // 查询数据

          String query = "SELECT * FROM users WHERE id = ?";

          try (PreparedStatement ps = conn.prepareStatement(query)) {

              ps.setBytes(1, UuidUtils.toBytes(userId));

              ResultSet rs = ps.executeQuery();

              while (rs.next()) {

                  UUID id = UuidUtils.fromBytes(rs.getBytes("id"));

                  String name = rs.getString("name");

              }

          }



          五、关于UUIDv7 常见问题解答


          1、UUIDv7到底会不会重复


          极低概率,可视为唯一


          UUIDv7由48位毫秒级Unix时间戳(约8.5万年后才会耗尽) + 74位随机数组成,总组合数达2^122(约5.3×10^36)。即使每秒生成10亿个UUID,重复概率也远低于10^-15,理论上可忽略。


          极端场景,若系统时钟回拨且在同一毫秒内生成大量UUID(超过2^74个),可能冲突,但实际中几乎不可能发生。


          2、什么是时钟回拨?对UUIDv7有何影响?


            原因:服务器时间因NTP同步错误、电源波动、虚拟机宿主机调整等意外回退。


            问题:时钟回拨后,新生成的UUIDv7时间戳可能小于前值,若回拨期间随机数碰撞则可能重复


            如何解决呢?


          1)预防措施:


            使用冗余时钟源:如GPS+原子钟+NTP多层级同步,减少单点故障。

            监控时钟漂移:通过Kalman滤波等算法实时修正时间偏差。

            避免虚拟机时钟漂移:优先部署于物理机。


          2)生成时容错:


            时间戳延续:检测到回拨时,延续最后记录的时间戳直至超过回拨区间。

            随机数扩容:回拨期间扩展随机数位数(如占用预留位),降低碰撞概率


          作者丨捡田螺的小男孩

          来源丨公众号:捡田螺的小男孩(ID:gh_3d11c9893ca0)

          dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn

          阅读原文

          跳转微信打开

          Fish AI Reader

          Fish AI Reader

          AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

          FishAI

          FishAI

          鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

          联系邮箱 441953276@qq.com

          相关标签

          UUIDv7 数据库主键 时间有序 分布式系统
          相关文章