MySQL 分布式 ID 方案全解析
分布式 ID 是解决「分库分表 ID 冲突」「自增 ID 上限」「跨节点 ID 唯一」的核心方案,本质是生成全局唯一、有序(可选)、高性能 的 ID。下面从「主流方案对比」「落地实现」「选型建议」三个维度,给出可直接落地的解决方案。
一、主流分布式 ID 方案对比(核心)
先通过表格快速掌握各方案的优缺点和适用场景,避免踩坑:
| 方案类型 | 核心原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 雪花算法(Snowflake) | 64位长整型:时间戳(41)+机器ID(10)+序列号(12) | 高性能(本地生成)、有序、无依赖、容量大 | 依赖服务器时钟,时钟回拨会导致ID重复 | 绝大多数业务(推荐):订单、用户、交易表 |
| 数据库分段 ID | 按业务/机器分配 ID 区间(如1-1000万) | 实现简单、有序、可追溯 | 依赖数据库,性能受限于DB,扩容复杂 | 中小规模业务、对性能要求不高的场景 |
| UUID/GUID | 基于网卡MAC+时间戳+随机数生成36位字符串 | 完全去中心化、实现极简单、无上限 | 字符串索引性能差、无序、占用空间大 | 非核心业务:日志、临时表、关联表 |
| 数据库自增 ID 改造 | 单库单表生成自增 ID,分库分表时设置步长 | 兼容原有自增逻辑、有序、易理解 | 性能瓶颈在主库、扩容麻烦、有单点风险 | 从单库迁移到分库分表的过渡场景 |
| 号段模式(美团Leaf) | 预生成 ID 号段缓存到本地,用完再申请 | 性能高(缓存)、有序、可扩容、无时钟依赖 | 实现稍复杂、依赖中间件(如Redis/DB) | 高并发核心业务:电商订单、支付流水 |
| Redis 自增 ID | 利用 Redis 的 INCR/INCRBY 原子性生成 | 高性能、原子性、无锁 | 依赖Redis、断电易丢失(需持久化)、无序 | 高频写场景:秒杀、库存、计数器 |
二、核心方案落地实现(可直接用)
方案 1:雪花算法(Snowflake)—— 推荐首选
1. 核心结构(64位长整型)
雪花算法生成的 ID 是一个 BIGINT 类型的数字,结构如下(可自定义调整位数):
0(符号位) + 41位时间戳(毫秒) + 10位机器ID + 12位序列号
- 符号位:固定 0,保证 ID 为正数;
- 时间戳:41位可表示约 69 年(2^41-1 毫秒),足够覆盖业务周期;
- 机器ID:10位可部署 1024 个节点(分布式场景不冲突);
- 序列号:12位每毫秒可生成 4096 个 ID,满足高并发。
2. 落地实现(Java 示例,MySQL 可直接存储)
雪花算法无需依赖 MySQL,由应用层生成后插入数据库,示例代码(简化版):
public class SnowflakeIdGenerator {
// 起始时间戳(2024-01-01 00:00:00),可自定义
private final static long START_TIMESTAMP = 1704067200000L;
// 机器ID位数(10位)
private final static long MACHINE_BIT = 10;
// 序列号位数(12位)
private final static long SEQUENCE_BIT = 12;
// 机器ID最大值(1023)
private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
// 序列号最大值(4095)
private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
// 位移:时间戳左移22位,机器ID左移12位
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private long machineId; // 机器ID
private long sequence = 0L; // 序列号
private long lastTimestamp = -1L; // 上一次生成ID的时间戳
// 构造函数:传入机器ID(0-1023)
public SnowflakeIdGenerator(long machineId) {
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("机器ID超出范围");
}
this.machineId = machineId;
}
// 生成下一个ID
public synchronized long nextId() {
long currTimestamp = System.currentTimeMillis();
// 时钟回拨检查
if (currTimestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨,拒绝生成ID");
}
// 同一毫秒,序列号自增
if (currTimestamp == lastTimestamp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
// 同一毫秒序列号用完,等待下一毫秒
if (sequence == 0L) {
currTimestamp = getNextMill();
}
} else {
// 不同毫秒,序列号重置为0
sequence = 0L;
}
lastTimestamp = currTimestamp;
// 拼接ID
return (currTimestamp - START_TIMESTAMP) << TIMESTAMP_LEFT // 时间戳部分
| machineId << MACHINE_LEFT // 机器ID部分
| sequence; // 序列号部分
}
// 等待下一毫秒
private long getNextMill() {
long mill = System.currentTimeMillis();
while (mill <= lastTimestamp) {
mill = System.currentTimeMillis();
}
return mill;
}
// 测试
public static void main(String[] args) {
SnowflakeIdGenerator generator = new SnowflakeIdGenerator(1); // 机器ID=1
for (int i = 0; i < 10; i++) {
long id = generator.nextId();
System.out.println("生成的分布式ID:" + id);
// 插入MySQL:直接将id作为主键插入BIGINT类型字段
// INSERT INTO order (id, order_no) VALUES (id, 'xxx');
}
}
}
3. MySQL 适配
- 表字段类型设置为
BIGINT UNSIGNED(避免溢出); - 主键索引直接建在该字段上,性能与自增 ID 一致。
方案 2:数据库分段 ID(简单易落地)
1. 核心思路
创建一个「ID 分配表」,按业务分配 ID 区间(如订单表分配 1-1000 万,用户表分配 1001 万-2000 万),应用层获取区间后本地生成 ID,用完再申请新区间。
2. 落地实现
-- 1. 创建ID分配表
CREATE TABLE id_generator (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
biz_type VARCHAR(32) NOT NULL COMMENT '业务类型(如order/user)',
max_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当前最大ID',
step INT NOT NULL DEFAULT 10000 COMMENT '步长(每次申请1万个ID)',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_biz_type (biz_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 2. 初始化业务ID(订单表)
INSERT INTO id_generator (biz_type, max_id, step) VALUES ('order', 0, 10000);
-- 3. 申请ID区间的存储过程(原子操作,避免并发冲突)
DELIMITER //
CREATE PROCEDURE apply_id(IN biz_type VARCHAR(32), OUT start_id BIGINT, OUT end_id BIGINT)
BEGIN
-- 行锁:避免并发申请同一业务的ID
UPDATE id_generator
SET max_id = max_id + step
WHERE biz_type = biz_type;
-- 查询申请到的区间
SELECT max_id - step + 1, max_id INTO start_id, end_id
FROM id_generator
WHERE biz_type = biz_type;
END //
DELIMITER ;
-- 4. 测试:申请订单表的ID区间
CALL apply_id('order', @start_id, @end_id);
SELECT @start_id AS 起始ID, @end_id AS 结束ID;
-- 输出:起始ID=1,结束ID=10000(下次申请为10001-20000)
3. 应用层逻辑
- 调用存储过程获取 ID 区间(如 1-10000);
- 本地维护当前 ID(从 1 到 10000 自增);
- 用完 10000 后,再次调用存储过程申请新区间。
方案 3:UUID 简化版(最快落地)
如果对性能要求不高,可直接用 MySQL 内置函数生成数字型 UUID,避免字符串索引性能问题:
-- 1. 生成64位数字UUID(UUID_SHORT())
SELECT UUID_SHORT(); -- 输出示例:923957845781234567
-- 2. 插入数据时直接使用
INSERT INTO log (id, content) VALUES (UUID_SHORT(), '操作日志');
-- 3. 表字段设置
CREATE TABLE log (
id BIGINT UNSIGNED NOT NULL DEFAULT UUID_SHORT(),
content VARCHAR(255) NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
⚠️ 注意:UUID_SHORT() 生成的 ID 无序,大批量插入会导致索引碎片,不建议用于核心业务表。
方案 4:Redis 自增 ID(高并发场景)
利用 Redis 的 INCR 原子性生成 ID,适合秒杀、库存等高频写场景:
# 1. Redis 命令(原子自增)
127.0.0.1:6379> INCR order_id # 每次调用+1,返回新ID
(integer) 1
# 2. 设置初始值(可选)
127.0.0.1:6379> SET order_id 10000 # 从10000开始自增
OK
// Java 示例(Jedis)
public class RedisIdGenerator {
private Jedis jedis;
private String key;
public RedisIdGenerator(Jedis jedis, String key) {
this.jedis = jedis;
this.key = key;
}
public long nextId() {
return jedis.incr(key);
}
// 测试
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
RedisIdGenerator generator = new RedisIdGenerator(jedis, "order_id");
long id = generator.nextId();
System.out.println("Redis生成的ID:" + id);
}
}
三、选型建议(避坑指南)
1. 优先选雪花算法的场景
- 核心业务(订单、用户、交易);
- 分库分表场景;
- 要求 ID 有序(便于排序/分页);
- 高并发(每秒万级以上)。
2. 选数据库分段 ID 的场景
- 中小规模业务;
- 团队技术栈以 MySQL 为主,不想引入额外组件;
- 要求 ID 可追溯、可人工调整。
3. 选 UUID 的场景
- 非核心业务(日志、临时表);
- 完全去中心化,不想依赖任何中间件;
- 对 ID 有序性、性能无要求。
4. 选 Redis 自增 ID 的场景
- 高频写场景(秒杀、库存);
- 临时计数器(如活动参与人数);
- 允许 ID 无序。
四、避坑要点
- 雪花算法时钟回拨:可通过「缓存最近生成的 ID」「时钟同步」「降级方案」解决;
- 数据库分段 ID 并发冲突:必须用行锁(
UPDATE后SELECT)或乐观锁; - ID 类型统一:MySQL 中统一用
BIGINT UNSIGNED,避免溢出; - 避免过度设计:中小业务优先用雪花算法或数据库分段 ID,无需引入复杂中间件。
总结
- 首选方案:雪花算法(本地生成、高性能、有序,适配绝大多数业务);
- 简易方案:数据库分段 ID(依赖 MySQL,实现简单)或 UUID(零依赖,非核心业务);
- 高并发场景:Redis 自增 ID 或美团 Leaf 号段模式;
- 核心原则:优先保证 ID 全局唯一,其次考虑性能、有序性、可维护性。
百流积聚,江河是也;文若化风,可以砾石。

浙公网安备 33010602011771号