JPA中Model主键使用UUID

前言

使用Spring JPA项目进行开发,有关Model的设计,PK如何选择一直是个课题,特别对于集群和微服务的部署架构引入进来之后,一直在找一个平衡的解决方案,最近一个项目中又深入讨论了这个问题,参考国外一个博客https://phauer.com/2016/uuids-hibernate-mysql/的内容,翻译整理如下,供以后参考。

关于数据库PK和UUID选择(MySQL)

  • 参考翻译自:https://phauer.com/2016/uuids-hibernate-mysql/

  • 目的:考量数据库PK的选择和UUID的使用

  • 使用UUID的优势:

    • UUID全局唯一;
    • 跨数据库唯一,合并数据,没有主键冲突;
    • 分布式复制和存储(分布式数据库)方便简单;
    • 可以随时生成UUID,不需要数据库支撑,方便测试和批量数据生成;
    • 自动增量ID容易猜测会导致安全问题。
  • 使用UUID的缺点:

    • 数据库索引的空间大小增大;单个UUID需要16个字节,普通int只需要4个字节;
    • 使用数据库SQL进行特定数据查询时会更加麻烦,下面会详细描述;
    • REST接口中使用UUID会增加payload的大小。
  • 使用UUID创建表:

    • 不建议使用VARCHAR(36)作为UUID数据类型,建议使用BINARY(16)作为字段类型;会降低空间占用,16个字节,没有字符串中的中线字符,数据库索引也会更快;
    • 不过,这样会造成SQL查询稍稍复杂些。
    1
    2
    3
    4
    CREATE TABLE product (
    `id` BINARY(16) NOT NULL primary key
    ,`name` varchar(64)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • JPA映射:

    1
    2
    3
    4
    @Id
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @Column(columnDefinition = "BINARY(16)")
  • SQL脚本生成UUID

    1
    SELECT uuid(); /*dbe07414-49d1-11e6-b7a7-0242ac140002*/
  • 插入UUID数据

    • uuid()函数返回字符串,包含中线字符,需要使用replace去掉中线字符,然后使用unhex()函数转换十六进制可看字符到UUID数值二进制,最终存入BINARY(16)字段中。
    1
    2
    3
    4
    INSERT INTO product VALUES(
    unhex(replace(uuid(), '-', ''))
    , "car"
    );
  • 查询UUID

    • 使用hex()函数转换UUID到十六进制字符表示。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    SELECT id, name FROM product;
    /* BLOB, 'car' */
    SELECT hex(id), name FROM product;
    /* 'BFF641BA9F3A4584A1BA53824E7AB3B9', 'car' */

    /* 通过特定UUID进行指定Row数据查询 */
    SELECT hex(id), name FROM product
    WHERE id = unhex('BFF641BA9F3A4584A1BA53824E7AB3B9');

    /* or if you have a UUID with dashes: */
    SELECT hex(id), name FROM product
    WHERE id = unhex(replace("2b08e375-275d-473e-910d-32700e34b61a", '-', ''));
  • 小结

    • 在工程的角度,使用UUID作为Model的主键,应该是利大于弊;
    • 使用BINARY(16)存储,性能损耗在可接受范围;
    • 对于SQL层面操作的不便,个人认为可以忍受(不过不同DB操作语言可能会有差异)。