主键生成策略:自增、selectKey 与 UUID/雪花

数据表都要有主键,但"谁来生成主键"容易被含糊带过:是数据库自增,还是应用生成?选择不同,插入后能否拿回主键、分布式下是否冲突,结果完全不同。本节梳理自增、selectKey、UUID 与雪花四种思路及推荐用法。

主键来源只有两类

  • 数据库生成:AUTO_INCREMENT(MySQL)、序列(Oracle)、数据库函数等,插入时自动赋值。
  • 应用生成:程序里用 UUID、雪花算法等算好值再 INSERT,不依赖数据库。

MyBatis 本身不生成主键,只负责把数据库生成的值回填到实体,或用你传入的值执行插入。

自增主键:useGeneratedKeys

自增主键在 MySQL 最常见,插入后要用 useGeneratedKeys + keyProperty 把数据库回填的 id 写进实体属性:

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user (name, email)
    VALUES (#{name}, #{email})
</insert>

接口方法把实体传进去,插入后 id 已被回填:

User user = new User();
user.setName("小明");
userMapper.insert(user);
System.out.println(user.getId()); // 输出:数据库回填的自增 id,如 1

要点:useGeneratedKeys 依赖 JDBC 驱动支持返回生成键(MySQL 支持);keyProperty实体属性名而非列名;批量插入时部分数据库只回填第一条记录,具体以官方文档为准。

selectKey:先从数据库取主键

Oracle 序列、数据库函数生成 key 的场景,用 <selectKey> 先查再插或插后回查:

<insert id="insertUser">
    <selectKey keyProperty="id" resultType="string" order="BEFORE">
        SELECT REPLACE(UUID(), '-', '')   -- MySQL 生成 32 位 UUID
    </selectKey>
    INSERT INTO user (id, name) VALUES (#{id}, #{name})
</insert>
  • order="BEFORE":先执行 selectKey 再 INSERT,适合 UUID、序列等"插入前就要值"的场景;
  • order="AFTER":先 INSERT 再查生成键,适合驱动不支持 useGeneratedKeys 的数据库;
  • Oracle 的典型写法是 SELECT seq_user.NEXTVAL FROM DUAL,各数据库语句以官方文档为准。

UUID 与雪花:应用生成

UUIDjava.util.UUID 直接生成,全局唯一、不依赖数据库;缺点是无序且偏长,做主键会拉低 InnoDB 聚簇索引的写入与查询性能:

user.setId(UUID.randomUUID().toString().replace("-", "")); // 32 位十六进制字符串

雪花算法(Snowflake):64 位 long,由时间戳 + 机器标识 + 序列号拼成,趋势递增、无中心依赖,适合分布式。实现细节多,业界普遍直接用现成组件,例如 MyBatis-Plus 的 IdType.ASSIGN_ID 即内置雪花实现(用法以官方文档为准):

@TableId(type = IdType.ASSIGN_ID) // MP:id 由雪花算法自动生成
private Long id;

如何选择

场景推荐理由
单库单体、不分表自增简单、有序、索引友好
需插入前知道 ID、客户端生成UUID无中心依赖
分布式、高并发写雪花(ASSIGN_ID)趋势递增、全局唯一

自增适合单库,UUID 与雪花面向分布式。数据库侧回填用 keyProperty,应用侧生成靠 IdType,两条路各掌握一种,遇到不熟悉的数据库以官方文档为准。

笔记加载中…