主键生成策略

主键生成策略

ssa|2026年5月25日 · 44 分钟

关系型数据库主键生成策略

主键是关系型数据库表的行唯一标识。选择合适的主键生成策略直接影响写入性能、索引效率、以及分布式扩展能力。


1. 主键选型的核心考量

1.1 评估维度

维度 说明 为什么重要
有序性 ID 是否趋势递增 有序 ID 减少 B+ 树页分裂,写入性能更高
全局唯一 分布式环境下是否保证唯一 分库分表后单库自增不再唯一
存储效率 ID 占用字节大小 主键作为二级索引的叶子节点值,越短越省空间
插入性能 批量插入是否支持 IDENTITY 模式下 JDBC 批量插入失效
可读性 ID 是否携带业务信息 订单号、流水号需要语义化
依赖程度 是否依赖外部组件 雪花算法纯本地生成,号段模式依赖数据库

1.2 核心结论

InnoDB 聚簇索引决定了主键必须是有序、短小的整型。UUID 和无序雪花 ID 直接做主键会导致频繁页分裂,写入性能急剧下降。


2. 数据库原生机制

2.1 MySQL — AUTO_INCREMENT

MySQL 通过 AUTO_INCREMENT 属性实现自增主键,是 InnoDB 下最常用的方案。

sql 复制代码
CREATE TABLE t_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL
);

-- 查看当前自增值
SHOW TABLE STATUS LIKE 't_user';

-- 手动设置起始值
ALTER TABLE t_user AUTO_INCREMENT = 10000;

工作原理

  • InnoDB 为每张含有 AUTO_INCREMENT 列的表维护一个计数器,存储在内存中
  • MySQL 8.0 前,自增值持久化在 redo log 中(重启后可能回退);8.0+ 改为写入数据字典,重启不丢失
  • 每次插入时自增值 +1,即使事务回滚,已分配的值也不会复用(存在空洞)

InnoDB 锁机制

插入类型 锁策略 并发影响
简单 INSERT(已知行数) 轻量级互斥锁,分配后立即释放 高并发下无瓶颈
批量 INSERT(未知行数) AUTO-INC 表级锁,语句结束释放 并发性能差
innodb_autoinc_lock_mode = 2 全部使用轻量级锁(交错模式) 性能最好,但 binlog 为 STATEMENT 时主从不安全

局限性

  • 分库分表后无法保证全局唯一
  • 批量插入需要逐条获取 ID,JDBC rewriteBatchedStatements 无法优化 INSERT
  • 自增值在主从切换后可能冲突

2.2 PostgreSQL — SEQUENCE / SERIAL

PostgreSQL 使用 SEQUENCE(序列对象)生成自增值,SERIAL / BIGSERIAL 是语法糖。

sql 复制代码
-- 方式一:SERIAL 语法糖(自动创建序列 + 设置默认值)
CREATE TABLE t_user (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL
);
-- 等价于:
-- CREATE SEQUENCE t_user_id_seq;
-- id BIGINT PRIMARY KEY DEFAULT nextval('t_user_id_seq');

-- 方式二:手动创建序列(更灵活)
CREATE SEQUENCE t_user_seq START WITH 10000 INCREMENT BY 1;

CREATE TABLE t_user (
    id BIGINT PRIMARY KEY DEFAULT nextval('t_user_seq'),
    username VARCHAR(50) NOT NULL
);

-- 批量分配:预取一批 ID
SELECT nextval('t_user_seq') FROM generate_series(1, 100);

相比 MySQL AUTO_INCREMENT 的优势

特性 MySQL AUTO_INCREMENT PostgreSQL SEQUENCE
预分配 不支持 ALTER SEQUENCE ... CACHE 1000
步长调整 不支持(固定+1) INCREMENT BY N 自定义步长
起始值 ALTER TABLE 修改 START WITH N
循环使用 不支持 CYCLE / NO CYCLE
批量获取 不支持 nextval() + generate_series()
与表耦合 否(独立对象,可多表共享)

PostgreSQL 10+ 的 IDENTITY 列

sql 复制代码
-- 推荐写法(取代 SERIAL)
CREATE TABLE t_user (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(50) NOT NULL
);

-- 优势:防止手动插入覆盖序列值
-- GENERATED ALWAYS:禁止手动插入
-- GENERATED BY DEFAULT:允许手动插入时覆盖

2.3 Oracle — SEQUENCE

Oracle 的 SEQUENCE 是功能最丰富的实现,也是 Oracle 数据库主键生成的标准方案。

sql 复制代码
-- 创建序列
CREATE SEQUENCE t_user_seq
    START WITH 1
    INCREMENT BY 1
    CACHE 500          -- 预缓存500个值,减少递归调用
    NOORDER            -- 不保证顺序(RAC 环境下提升性能)
    NOCYCLE;

-- 使用序列
INSERT INTO t_user (id, username) VALUES (t_user_seq.NEXTVAL, '张三');

-- 获取当前会话最后一次 NEXTVAL 的值
SELECT t_user_seq.CURRVAL FROM dual;

RAC 集群下的特殊处理

参数 说明 性能影响
ORDER 保证全局有序(跨节点) 性能较低,需要跨节点协调
NOORDER 各节点独立分配,不保证全局有序 性能高,推荐用于非业务排序场景
CACHE 每个节点缓存一段值 值越大,性能越好,但重启浪费越多

2.4 三大数据库对比

特性 MySQL PostgreSQL Oracle
自增方式 AUTO_INCREMENT SEQUENCE / SERIAL / IDENTITY SEQUENCE
预分配缓存 不支持 CACHE N CACHE N
步长自定义 不支持 INCREMENT BY N INCREMENT BY N
批量获取 ID 不支持 generate_series + nextval 不原生支持
集群支持 主从复制 流复制 / 逻辑复制 RAC(ORDER/NOORDER
有序性保证 单实例严格递增 单实例严格递增 ORDER 模式下全局有序

3. UUID

3.1 生成方式

java 复制代码
// Java 原生 UUID(版本4,随机)
UUID uuid = UUID.randomUUID();        // 36字符:550e8400-e29b-41d4-a716-446655440000
String id = uuid.toString().replace("-", "");  // 32字符:550e8400e29b41d4a716446655440000

// MySQL 内置 UUID
SELECT UUID();  -- 550e8400-e29b-41d4-a716-446655440000

-- MySQL 8.0+:有序 UUID(替换时间戳字节顺序)
SELECT UUID_TO_BIN(UUID(), TRUE);  -- 16字节二进制,有序

3.2 UUID v7(2023年 RFC 9562)

UUID v7 解决了 UUID v4 无序的问题,以毫秒级时间戳开头,趋势递增

复制代码
UUID v7 结构(128位):
| 48位毫秒时间戳 | 4位版本(0111) | 12位随机 | 2位变体 | 62位随机 |
sql 复制代码
-- PostgreSQL 17+ 原生支持
SELECT gen_random_uuid();  -- UUID v4
-- UUID v7 需扩展或自定义函数

-- MySQL 8.0 中生成有序 UUID
SELECT UUID_TO_BIN(UUID(), TRUE) AS ordered_uuid;

3.3 为什么 UUID 不适合做 InnoDB 主键

复制代码
问题链:
UUID 无序 → 每次插入落到 B+ 树随机位置
  → 频繁页分裂(Page Split)
    → 大量随机 I/O
      → 写入性能严重下降

此外:
UUID 36字符 → 二级索引叶子节点存储主键值
  → 索引膨胀(36字节 vs BIGINT 8字节)
    → 内存利用率低,磁盘占用大

性能对比数据(参考 Percona 基准测试):

主键类型 插入 QPS 表大小 二级索引大小
BIGINT AUTO_INCREMENT ~25,000 100MB 50MB
BINARY(16) UUID ~12,000 120MB 80MB
CHAR(36) UUID ~8,000 160MB 120MB

4. 雪花算法(Snowflake)

4.1 结构设计

复制代码
Snowflake ID 结构(64位 = 8字节 = Java long):

 0 | 00000000 00000000 00000000 00000000 00000000 0 | 00000 00000 | 000000000000
 │  │                   41位时间戳                   │  10位机器ID  │   12位序列号  │
符号位                                                │(5位数据中心+5位机器)│

- 每毫秒可生成 4096 个 ID(单节点)
- 可用 69 年(从纪元开始计算)
- 支持 1024 个节点(32 个数据中心 × 32 台机器)

4.2 Java 实现

java 复制代码
public class SnowflakeIdGenerator {
    private final long epoch = 1609459200000L; // 2021-01-01 起始纪元
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long sequenceBits = 12L;

    private final long maxWorkerId = ~(-1L << workerIdBits);        // 31
    private final long maxDatacenterId = ~(-1L << datacenterIdBits); // 31
    private final long sequenceMask = ~(-1L << sequenceBits);        // 4095

    private final long workerIdShift = sequenceBits;                           // 12
    private final long datacenterIdShift = sequenceBits + workerIdBits;         // 17
    private final long timestampShift = sequenceBits + workerIdBits
                                        + datacenterIdBits;                    // 22

    private final long workerId;
    private final long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("worker Id error");
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException("datacenter Id error");
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();

        // 时钟回拨检测
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards");
        }

        // 同一毫秒内,序列号自增
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - epoch) << timestampShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}

4.3 时钟回拨问题

核心风险:NTP 时间同步可能导致系统时钟回退,产生重复 ID。

策略 做法 适用场景
抛异常 回拨超过阈值直接报错 对唯一性要求极高
等待追上 小幅回拨(<50ms)时自旋等待 生产环境推荐
备用 workerId 回拨时切换到预分配的备用机器 ID 高可用场景
百度 UidGenerator 改用未来时间填补,环形缓冲区 时钟回拨频繁的环境

4.4 雪花 ID 与数据库主键

关键问题:雪花 ID 做主键好吗?

雪花 ID 是趋势递增的(同一毫秒内严格递增,跨毫秒也递增),相比 UUID 好很多,但不如纯自增 ID 严格有序。对 InnoDB 聚簇索引来说:

  • 可以接受:趋势递增,页分裂远少于 UUID
  • 非最优:不如纯自增 ID 的顺序写入效率
  • 折中方案:主键用 BIGINT AUTO_INCREMENT,雪花 ID 存业务字段(如 order_no

5. 号段模式

5.1 核心思想

从数据库批量预分配一段 ID,应用在内存中消费,用完再取下一段。

复制代码
号段模式工作流:

  应用启动
    ↓
  从数据库获取号段 [1, 1000](step=1000)
    ↓
  内存中分配:1, 2, 3, ..., 999
    ↓
  消耗到 10% 时,异步预加载下一段 [1001, 2000]
    ↓
  当前号段用完 → 无缝切换到备用号段

5.2 号段表设计

sql 复制代码
CREATE TABLE id_segment (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    biz_tag VARCHAR(64) NOT NULL COMMENT '业务标识(如 order、user)',
    max_id BIGINT NOT NULL DEFAULT 1 COMMENT '当前最大已分配 ID',
    step INT NOT NULL COMMENT '步长(每次预分配的 ID 数量)',
    version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
    description VARCHAR(256) COMMENT '描述',
    update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
                 ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_biz_tag (biz_tag)
) ENGINE=InnoDB;
sql 复制代码
-- 获取号段(乐观锁 + CAS 更新)
UPDATE id_segment
SET max_id = max_id + step,
    version = version + 1
WHERE biz_tag = 'order' AND version = #{currentVersion};

-- 获取更新后的 max_id
SELECT max_id, step FROM id_segment WHERE biz_tag = 'order';
-- 号段范围:[max_id - step + 1, max_id]

5.3 双 Buffer 优化

复制代码
  ┌──────────────┐     ┌──────────────┐
  │  当前号段      │     │  备用号段      │
  │  [1, 1000]    │     │  [1001, 2000] │
  │  消耗中...     │     │  已预加载 ✓    │
  └──────────────┘     └──────────────┘

当前号段消耗到 10% → 触发异步加载备用号段
当前号段用完 → 原子切换到备用号段 → 旧备用变为当前,开始加载新备用

优势:数据库短暂不可用时,应用仍可从缓存号段中分配 ID,提供缓冲期。


6. TSID(Time-Sorted ID)

6.1 什么是 TSID

TSID(Time-Sorted ID)是一种 64 位时间排序的唯一标识符,由 Fabio Lima 设计,Vlad Mihalcea 提供 Hibernate 集成。它结合了雪花算法的紧凑性和 ULID 的简洁性,是 Snowflake 的现代演进方案。

与雪花算法的核心区别

维度 雪花算法 TSID
机器 ID 必须预分配 workerId / datacenterId 可随机生成,无需协调
字符串表示 纯数字(如 1541815603606036480 Crockford Base32 编码,13 字符(如 0HJBZJM5V3YC8
单调性 同毫秒内序列号递增 同毫秒内计数器递增,可跨越毫秒保持单调
时钟回拨 直接抛异常 允许时间组件领先系统时钟 1ms+ 来维持单调性
数据库存储 BIGINT BIGINT(一致)

6.2 结构设计

复制代码
TSID 结构(64位 = Java long):

 adjustable
<---------->
----------┼----------┼----------┼----------
 time (42 bits)       node       counter
                      (10 bits)  (12 bits)

- time:毫秒时间戳,自 2020-01-01 起算
  42 位 → 有符号 long 可用 ~69 年,无符号 ~139 年
- node:节点标识,0~20 位可调(默认 10 位,支持 1024 节点)
- counter:同一毫秒内的计数器,初始值随机
  node bits + counter bits = 22 位(固定)
  → node=10 时 counter=12,每毫秒 4096 个 ID
  → node=0 时 counter=22,每毫秒 419 万个 ID(单机模式)

6.3 Java 使用

依赖(两个主流实现任选其一):

xml 复制代码
<!-- 方式一:Hypersistence TSID(Vlad Mihalcea,提供 Hibernate 集成) -->
<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-tsid</artifactId>
    <version>2.1.3</version>
</dependency>

<!-- 方式二:TSID Creator(Fabio Lima,原版实现) -->
<dependency>
    <groupId>com.github.f4b6a3</groupId>
    <artifactId>tsid-creator</artifactId>
    <version>5.2.6</version>
</dependency>

基础用法

java 复制代码
import io.hypersistence.tsid.Tsid;

// 快速生成(使用静态工厂)
Tsid tsid = Tsid.fast();

// 获取数值(存入数据库 BIGINT)
long id = tsid.toLong();           // 如 809100737063473402

// 获取字符串(13字符,Crockford Base32)
String str = tsid.toString();      // 如 "0PEM0TNJ2SM7T"

// 从 ID 反推创建时间
Instant createdAt = tsid.getInstant();

自定义工厂配置

java 复制代码
// 场景一:指定节点 ID(分布式环境多节点)
int node = 256;   // 最大值:2^nodeBits - 1
Tsid.Factory factory = Tsid.Factory.builder()
        .withNodeBits(10)   // 节点位:0~20 可调
        .withNode(node)
        .build();

// 场景二:单机模式(无需节点 ID,最大化每毫秒 ID 数)
Tsid.Factory factory = Tsid.Factory.builder()
        .withNodeBits(0)    // 无节点位,counter 占 22 位
        .build();           // 每毫秒可生成 419 万个 ID

// 场景三:自定义纪元
Instant customEpoch = Instant.parse("2000-01-01T00:00:00Z");
Tsid.Factory factory = Tsid.Factory.builder()
        .withCustomEpoch(customEpoch)
        .build();

// 场景四:模拟 Twitter Snowflake 结构
int datacenter = 1, worker = 1;
int snowflakeNode = (datacenter << 5) | worker;
Tsid.Factory factory = Tsid.Factory.builder()
        .withCustomEpoch(Instant.ofEpochMilli(1288834974657L))
        .withNode(snowflakeNode)
        .withRandomFunction(x -> new byte[x])  // 计数器从 0 开始
        .build();

6.4 JPA / Hibernate 集成

方式一:@PrePersist 回调(最简单)

java 复制代码
@Entity
@Table(name = "t_order")
public class Order {

    @Id
    private Long id;

    private String orderNo;

    @PrePersist
    public void prePersist() {
        if (id == null) {
            id = Tsid.fast().toLong();
        }
    }

    // 从 ID 反推创建时间(无需额外的 create_time 字段)
    public Instant getCreatedAt() {
        return Tsid.from(id).getInstant();
    }
}

方式二:自定义 Hibernate IdentifierGenerator(更优雅)

java 复制代码
import io.hypersistence.tsid.Tsid;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;

public class TsidGenerator implements IdentifierGenerator {

    @Override
    public Long generate(SharedSessionContractImplementor session, Object object) {
        return Tsid.fast().toLong();
    }
}

// 实体中使用
@Entity
@Table(name = "t_order")
public class Order {

    @Id
    @GeneratedValue(generator = "tsid-generator")
    @GenericGenerator(name = "tsid-generator", strategy = "com.example.TsidGenerator")
    private Long id;
}

方式三:Spring Data JPA + BeforeConvertCallback(Spring Boot 3+)

java 复制代码
@Component
public class TsidIdListener {

    @BeforeConvert
    public void assignId(Object entity) {
        if (entity instanceof BaseEntity base && base.getId() == null) {
            base.setId(Tsid.fast().toLong());
        }
    }
}

6.5 TSID 字符串格式的优势

复制代码
连续生成的 TSID 字符串序列:

01226N0640J7K
01226N0640J7M
01226N0640J7N
01226N0640J7P
01226N0640J7Q
01226N0640J7R
01226N0640J7S
01226N0640J7T
01226N0693HDA  ← 毫秒切换
01226N0693HDB
01226N0693HDC
...
^^ ^          ^
│  │          │
│  │          └── 随机部分(node + counter)
│  └── 时间戳部分(毫秒)
└── 时间戳高位

特点:
- 字符串天然按时间排序(字典序 = 时间序)
- 仅 13 字符(UUID v4 需要 36 字符)
- 可安全用于 URL、日志、前端展示
- 兼容 JavaScript(Number 精度丢失问题 → 使用字符串)

7. 其他方案

7.1 Redis INCR

bash 复制代码
# 原子自增
INCR order:id          # 返回 1, 2, 3, ...
INCRBY order:id 1000   # 一次分配 1000 个号段

# 带过期时间(按天重置)
SET order:20260525:id 0 EX 86400
INCR order:20260525:id
优点 缺点
原子操作,高性能 依赖 Redis,Redis 故障则不可用
灵活控制步长 持久化配置不当可能丢失(RDB/AOF)
天然支持号段预分配 需要额外基础设施

7.2 数据库集群步长偏移

sql 复制代码
-- 两个数据库实例,步长为 2
-- 数据库 A:起始值 1,步长 2 → 生成 1, 3, 5, 7, ...
SET @@auto_increment_increment = 2;
SET @@auto_increment_offset = 1;

-- 数据库 B:起始值 2,步长 2 → 生成 2, 4, 6, 8, ...
SET @@auto_increment_increment = 2;
SET @@auto_increment_offset = 2;

局限:扩容困难(需要修改步长和起始值),ID 不连续。


8. ORM 框架集成

8.1 JPA / Hibernate 主键策略

JPA 通过 @GeneratedValue 注解定义主键生成策略,支持四种标准策略加 Hibernate 扩展:

java 复制代码
// 策略一:IDENTITY — 使用数据库自增(MySQL AUTO_INCREMENT, PostgreSQL SERIAL)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// 策略二:SEQUENCE — 使用数据库序列(Oracle, PostgreSQL)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
@SequenceGenerator(name = "user_seq",
                   sequenceName = "t_user_seq",
                   allocationSize = 1,     // 每次 nextval 调用分配的 ID 数
                   initialValue = 1)
private Long id;

// 策略三:TABLE — 用独立表模拟序列(跨数据库兼容,性能差)
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "user_table_gen")
@TableGenerator(name = "user_table_gen",
                table = "id_generator",
                pkColumnName = "gen_name",
                pkColumnValue = "user_id",
                valueColumnName = "gen_value",
                allocationSize = 50)
private Long id;

// 策略四:UUID(Hibernate 扩展,JPA 标准不内置)
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
@Column(columnDefinition = "VARCHAR(36)")
private String id;

allocationSize 与性能

allocationSize 行为 性能影响
1(默认) 每次插入都调用 nextval 高频数据库访问
50 预分配 50 个 ID 到内存 减少数据库访问,推荐设置

重要allocationSize 必须与数据库序列的 INCREMENT BY 值一致,否则会导致主键冲突。

java 复制代码
// 正确的配合使用
// 数据库端:CREATE SEQUENCE t_user_seq INCREMENT BY 50;
// JPA 端:
@SequenceGenerator(name = "user_seq",
                   sequenceName = "t_user_seq",
                   allocationSize = 50)  // 与 INCREMENT BY 一致

JPA 主键策略对比

策略 数据库支持 批量插入 性能 分布式
IDENTITY MySQL, PostgreSQL 不支持(需逐条获取) 一般 不支持
SEQUENCE Oracle, PostgreSQL 支持(预分配) 不支持
TABLE 所有 支持 较差(锁竞争) 不支持
UUID 所有 支持 较差(字符串索引) 支持

IDENTITY 与批量插入的矛盾:Hibernate 使用 IDENTITY 策略时,无法使用 JDBC 批量插入。因为 INSERT 后必须立即获取生成的 ID 才能管理实体状态,导致每条 INSERT 单独执行。这是选择 SEQUENCE 的重要理由。

8.2 MyBatis-Plus 主键策略

MyBatis-Plus 通过 @TableId 注解和全局配置控制主键生成:

java 复制代码
@Data
@TableName("t_user")
public class User {

    // 雪花算法(默认策略)
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
}

IdType 枚举

IdType 生成方式 说明
AUTO 数据库自增 对应 MySQL AUTO_INCREMENT
ASSIGN_ID 雪花算法(默认) 分布式唯一 ID,64位 long
ASSIGN_UUID UUID 去掉横线的 32 位 UUID
INPUT 手动输入 需要在插入前手动设置 ID
NONE 跟随全局配置 idType 全局设置决定
yaml 复制代码
# application.yml 全局配置
mybatis-plus:
  global-config:
    db-config:
      id-type: assign_id          # 全局主键策略(默认雪花算法)

8.3 框架选型建议

场景 推荐方案 ORM 配置
单库 MySQL + JPA GenerationType.IDENTITY @GeneratedValue(strategy = IDENTITY)
单库 Oracle/PG + JPA GenerationType.SEQUENCE + allocationSize @SequenceGenerator(allocationSize = 50)
分布式 + MyBatis-Plus 雪花算法(默认) IdType.ASSIGN_ID
分布式 + JPA TSID 自定义生成器 @GenericGenerator + TsidGenerator(见第 6 章)
订单号等业务 ID 号段模式(Leaf) 应用层独立服务

9. 方案总览与选型决策

9.1 全方案对比

方案 有序性 全局唯一 性能 存储效率 批量插入 依赖 适用场景
AUTO_INCREMENT 严格递增 单库唯一 8 字节 单库单表
SEQUENCE 严格递增 单库唯一 高(可预分配) 8 字节 Oracle/PG 单库
UUID v4 无序 全局唯一 36 字节 临时 ID、非索引字段
UUID v7 趋势递增 全局唯一 36 字节 兼容已有 UUID 方案
雪花算法 趋势递增 全局唯一 极高 8 字节 时钟 分布式系统经典方案
TSID 趋势递增 全局唯一 极高 8 字节 / 13字符 时钟 雪花算法的现代替代
号段模式 严格递增 全局唯一 8 字节 数据库 电商订单、高可用要求
Redis INCR 严格递增 全局唯一 8 字节 Redis 已有 Redis 基础设施

9.2 选型决策树

复制代码
开始
  ↓
是否分库分表 / 微服务?
  ├── 否 → 单库自增(MySQL AUTO_INCREMENT / PG SEQUENCE)
  └── 是
        ↓
      是否需要严格递增?
        ├── 否 → TSID(推荐)/ 雪花算法(无需协调、高性能)
        └── 是
              ↓
            是否对高可用有极高要求?
              ├── 否 → Redis INCR
              └── 是 → 号段模式(Leaf-segment)

9.3 主键 vs 业务 ID 分离

生产环境中常见的最佳实践:主键与业务 ID 分离

复制代码
t_order 表:
  id          BIGINT AUTO_INCREMENT PRIMARY KEY   -- 聚簇索引主键,有序写入
  order_no    BIGINT NOT NULL UNIQUE              -- 雪花算法生成的业务订单号
  ...

好处:
  1. 主键有序 → InnoDB 写入性能最优
  2. 业务 ID 全局唯一 → 分库分表、跨服务引用时使用
  3. 主键短小 → 二级索引紧凑,内存利用率高