分库分表之后,id 主键如何处理?
分库分表后,主键需满足 “全局唯一” 和 “有序性”(便于排序、分页),传统自增主键(单表自增)会因多表并行生成导致重复,常用解决方案如下:
# 1. 方案 1:UUID/GUID(全局唯一标识符)
- 原理:通过算法生成 128 位(UUIDv4)的全局唯一字符串(如
550e8400-e29b-41d4-a716-446655440000),无需数据库干预,直接在应用层生成; - 优点:
- 实现简单(Java 中
UUID.randomUUID()直接生成); - 无中心化依赖,分库分表节点可独立生成,支持水平扩展;
- 实现简单(Java 中
- 缺点:
- 无序性:UUID 是随机字符串,无法保证递增,插入数据库时会导致索引碎片(InnoDB 聚簇索引无序插入,效率低);
- 占用空间大:128 位字符串(36 字符),比整数主键占用更多存储和索引空间;
- 适用场景:对主键有序性无要求的场景(如日志表、标签表)。
# 2. 方案 2:雪花算法(Snowflake)
- 原理:生成 64 位长整型(Long)主键,结构如下(可自定义分段):
- 1 位符号位(固定 0,确保正数);
- 41 位时间戳(毫秒级,可使用 69 年);
- 10 位机器 ID(支持 1024 个节点,适配分库分表的多个节点);
- 12 位序列号(每个节点每毫秒可生成 4096 个 ID,避免并发冲突);
- 优点:
- 全局唯一:时间戳 + 机器 ID + 序列号组合,无重复;
- 有序递增:按时间戳递增,插入数据库时索引效率高(聚簇索引有序插入);
- 性能高:本地生成,无网络开销,支持高并发(每秒可生成百万级 ID);
- 缺点:
- 依赖系统时间:若系统时间回拨(如服务器时钟校准),可能生成重复 ID(需通过算法优化,如记录最后生成时间,回拨时等待或报错);
- 需配置机器 ID:需确保分库分表的每个节点机器 ID 唯一(如通过配置文件或注册中心分配);
- 适用场景:高并发、对主键有序性有要求的场景(如订单表、用户表),是分库分表的首选方案。
# 3. 方案 3:数据库自增主键(号段模式)
- 原理:搭建独立的 “主键生成数据库”,通过号段分配(如每次给分库分表节点分配一段 ID 区间,如 1-10000),节点在区间内自增生成主键,区间用完后再申请新号段;
- 实现示例:
- 主键生成表(
id_generator):存储表名、当前最大 ID、号段长度(如table_name='order', current_max_id=10000, step=10000); - 分库分表节点 A 申请号段:获取 1-10000,生成 ID 时从 1 自增到 10000;
- 节点 A 号段用完后,再次申请新号段(10001-20000);
- 主键生成表(
- 优点:
- 有序递增:符合数据库主键的递增特性,索引效率高;
- 无重复:号段全局唯一,节点间无重叠;
- 缺点:
- 依赖中心化服务:主键生成数据库是单点(需部署主从集群保证高可用);
- 性能瓶颈:高并发下,号段申请可能成为瓶颈(可通过增大号段长度减少申请次数);
- 适用场景:对主键有序性要求高、并发量中等的场景(如电商订单表)。
# 4. 方案 4:复合主键(分表键 + 自增主键)
- 原理:主键由 “分表键 + 单表自增主键” 组成,确保全局唯一(分表键不同,即使单表自增主键重复,复合主键也唯一);
- 示例:分表键为
user_id%8(分 8 张表),单表自增主键为id,复合主键为user_id%8 + "_" + id(如0_1001、1_1001); - 优点:实现简单,无需额外组件;
- 缺点:
- 主键长度长(字符串类型),索引效率低;
- 无序性:不同分表的自增主键独立,复合主键整体无序;
- 适用场景:分表键固定、对主键性能要求不高的场景(如用户收货地址表)。
# 5. 方案对比与选择
| 方案 | 全局唯一 | 有序性 | 性能 | 依赖组件 | 推荐度 |
|---|---|---|---|---|---|
| UUID/GUID | 是 | 否 | 高 | 无 | ★★★☆☆ |
| 雪花算法 | 是 | 是 | 高 | 无(需配置机器 ID) | ★★★★★ |
| 数据库自增(号段模式) | 是 | 是 | 中 | 主键生成数据库 | ★★★★☆ |
| 复合主键 | 是 | 否 | 中 | 无 | ★★★☆☆ |
- 首选:雪花算法(兼顾性能、唯一性、有序性,无中心化依赖);
- 次选:号段模式(适合对有序性要求极高、需避免时间回拨风险的场景);
- 慎用:UUID/GUID(仅适合对有序性无要求的场景)、复合主键(仅适合简单分表场景)。
上次更新: 12/30/2025