目录
- 分库分表后的主键ID冲突陷阱
- 一、方案一:数据库自增ID + 分段设置
- 核心思想
- 实现代码
- 代码注解
- Java调用示例
- 优缺点分析
- 二、方案二:UUID全局唯一标识符
- 核心思想
- 实现代码
- Java生成UUID
- 代码注解
- 性能优化示例
- 优缺点分析
- 三、方案三:Snowflake算法(Twitter开源)
- 核心思想
- Java实现代码
- 代码注解
- 调用示例
- 优缺点分析
- 四、方案四:Redis原子自增
- 核心思想
- Redis配置
- Java调用示例
- 代码注解
- 集群模式优化
- 优缺点分析
- 五、方案五:第三方ID生成服务
- 核心思想
- TinyID集成示例
- 代码注解
- TinyID服务端配置
- 优缺点分析
- 六、方案六:COMB ID(组合ID)
- 核心思想
- Java实现代码
- 代码注解
- 优缺点分析
- 七、方案七:数据库中间件内置策略
- ShardingSphere示例
- 代码注解
- 自定义生成器示例
- 优缺点分析
- 八、方案八:基因法 + Hash分片
- 核心思想
- Java实现代码
- 代码注解
- 分表查询示例
- 优缺点分析
- 如何选择最适合你的方案?
- 代码包结构
分库分表后的主键ID冲突陷阱
当你的mysql数据库因数据量或并发压力进行分库分表后,主键ID重复会成为系统崩溃的导火索。假设你将订单表拆分为10个分表,每个分表都从1开始自增,最终会出现以下灾难性问题:
- ID冲突:不同分表中的订单ID可能完全相同
- 业务混乱:无法通过ID直接定位数据所在分表
- 分布式事务失败:主键冲突导致写入操作异常
本文将从底层原理出发,结合真实业务代码,深度解析8种主流主键ID生成方案,助你构建稳定可靠的分库分表系统。
一、方案一:数据库自增ID + 分段设置
核心思想
通过为每个分表配置不同的起始值和步长,确保ID全局唯一。
实现代码
-- 分表1(t_order_0)配置 ALTER TABLE t_order_0 AUTO_INCREMENT = 1; -- 起始值1,步长10 -- 分表2(t_order_1)配置 ALTER TABLE t_order_1 AUTO_INCREMENT = 11; -- 起始值11,步长10 -- 分表3(t_order_2)配置 ALTER TABLE t_order_2 AUTO_INCREMENT = 21; -- 起始值21,步长10
代码注解
- 步长设置:若总分表数为N,则每个分表步长设为N
- 起始值计算:分表索引i的起始值为
i * N + 1
- 适用场景:分表数量固定的小规模系统
Java调用示例
// 根据订单ID计算分表索引 int getTableIndex(Long orderId) { return (int)(orderId % 10); // 假设分10个表 } // 插入订单 void insertOrder(Order order) { int tableIndex = getTableIndex(order.getId()); String tableName = "t_order_" + tableIndex; // 动态拼接SQL String sql = "INSERT INTO " + tableNandroidame + "(id, order_no) VALUES (?, ?)"; jdbcTemplate.update(sql, order.getId(), order.getOrderNo()); }
优缺点分析
优点 | 缺点 |
---|---|
实现简单,无需额外组件 | 分表数量固定,扩容困难 |
性能优秀,直接利用数据库特性 | 高并发下单表自增ID可能耗尽 |
存储效率高,ID紧凑 | 需手动维护分表配置 |
二、方案二:UUID全局唯一标识符
核心思想
利用UUID的128位随机性保证全局唯一性。
实现代码
-- 创建订单表(主键字段为UUID) CREATE TABLE t_order ( id CHAR(36) PRIMARY KEY, -- UUID长度36位 order_no VARCHAR(50) );
Java生成UUID
import java.util.UUID; public class Order { private String id = UUID.randomUUID().toString(); // 生成UUID private String orderNo; // Getter & Setter }
代码注解
- UUID格式:
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
- 存储优化:可压缩为16字节二进制存储(Base128编码)
性能优化示例
// 压缩UUID为16字节 public byte[] compressUuid(String uuid) { return UUID.fromString(uuid).toString().getBytes(StandardCharsets.UTF_8); }
优缺点分析
优点 | 缺点 |
---|---|
完全随机,无依赖数据库 | 存储空间大(36字节) |
适用于分布式系统 | B+树索引性能差(随机写入) |
无需协调生成ID | 无法直接定位分表 |
三、方案三:Snowflake算法(Twitter开源)
核心思想
通过时间戳+机器ID+序列号生成64位有序ID。
Java实现代码
public class SnowflakeIdGenerator { private final long twepoch = 1288834974657L; // 起始时间戳(2010-11-04) private final long workerIdBits = 5L; // 机器ID位数 private final long datacenterIdBits = 5L; // 数据中心ID位数 private final long sequenceBits = 12L; // 序列号位数 private final long maxWorkerId = ~(-1L << workerIdBits); private final long maxDatacenterId = ~(-1L << datacenterIdBits); private final long sequenceMask = ~(-1L << sequenceBits); private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; public SnowflakeIdGenerator(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException(String.format("时钟回拨: %d ms", lastTimestamp - timestamp)); } if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0; } lastTimestamp = timestamp; return (timestamp - twepoch) << (workerIdBits + datacenterIdBits + sequenceBits) | (datacenterId << (workerIdBits + sequenceBits)) | (workerId << sequenceBits) 编程 | sequence; } protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen() { return System.currentTimeMillis(); } }
代码注解
- 位运算逻辑:
- 时间戳(41位) → 代表从起始时间到现在的毫秒数
- 数据中心ID(5位) &rwww.devze.comarr; 支持32个数据中心
- 机器ID(5位) → 支持32个节点
- 序列号(12位) → 毫秒内最多支持4096个ID
调用示例
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1); long orderId = idGenerator.nextId(); System.out.println("生成的ID: " + orderId);
优缺点分析
优点 | 缺点 |
---|---|
全局唯一且有序 | 依赖系统时间(时钟回拨问题) |
支持分布式场景 | 需要预先分配机器ID |
存储效率高(8字节) | 最大时间戳限制为69年 |
四、方案四:Redis原子自增
核心思想
利用Redis的INCR命令生成全局唯一ID。
Redis配置
# 启动Redis redis-server # 设置初始值 SET order_id 1000
Java调用示例
import redis.clients.jedis.Jedis; public class RedisIdGenerator { private Jedis jedis = new Jedis("localhost"); public long generateId() { return jedis.incr("order_id"); // 原子自增 } }
代码注解
- 高可用保障:建议使用Redis集群模式
- 分片策略:可通过不同Key区分业务类型(如
order_id
、user_id
)
集群模式优化
// Redis Cluster客户端配置 JedisCluster jedisCluster = new JedisCluster(new HostAndPort("192.168.1.101", 6379), new HostAndPort("192.168.1.102", 6379));
优缺点分析
优点 | 缺点 |
---|---|
高性能(每秒百万级QPS) | 依赖Redis服务稳定性 |
支持分布式场景 | 单点故障风险(需集群) |
简单易用 | 需处理网络延迟 |
五、方案五:第三方ID生成服务
核心思想
使用独立服务(如滴滴TinyID)生成ID。
TinyID集成示例
// 1. 添加Maven依赖 <dependency> <groupId>com.didi</groupId> <artifactId>tinyid-client</artifactId> <version>1.0.0</version> </dependency> // 2. 配置TinyID TinyIdClient client = new TinyIdClient("http://tinyid-service.com"); // 3. 生成ID String businessType = "order"; Long id = client.getId(businessType);
代码注解
- TinyID原理:基于数据库的Sequence表生成ID
- 多租户支持:通过
businessType
区分不同业务
TinyID服务端配置
CREATE TABLE tinyid ( id BIGINT PRIMARY KEY, business_type VARCHAR(50), max_id BIGINT, step INT, version INT );
优缺点分析
优点 | 缺点 |
---|---|
支持多业务类型 | 需维护独立服务 |
高可用性(支持集群) | 依赖网络通信 |
灵活配置步长 | 学习成本较高 |
六、方案六:COMB ID(组合ID)
核心思想
将时间戳与随机数组合,生成有序UUID。
Java实现代码
import java.time.Instant; import java.util.Random; public class CombIdGenerator { private final Random random = new Random(); public String generateId() { byte[] uuid = new byte[16]; // 前6字节为时间戳 long timestamp = Instant.now().toEpochMilli(); for (int i = 0; i < 6; iwww.devze.com++) { uuid[i] = (byte)(timestamp >> (8 * (5 - i))); } // 后10字节为随机数 random.nextBytes(uuid); return bytesToHex(uuid); } private String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02x", b)); } return sb.toString(); } }
代码注解
- 时间戳部分:确保ID有序性
- 随机部分:避免重复
优缺点分析
优点 | 缺点 |
---|---|
兼具有序性和随机性 | 实现复杂度高 |
降低B+树索引碎片 | 依赖时间同步 |
七、方案七:数据库中间件内置策略
ShardingSphere示例
// 1. 配置ShardingSphere spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE spring.shardingsphere.sharding.tables.t_order.key-generator.props.worker.id=123
代码注解
- 内置算法:支持UUID和Snowflake
- 自定义扩展:可通过接口实现自定义生成器
自定义生成器示例
public class CustomKeyGenerator implements KeyGenerator { @Override public Comparable<?> generateKey() { return new SnowflakeIdGenerator(1, 1).nextId(); } }
优缺点分析
优点 | 缺点 |
---|---|
无缝集成ShardingSphere | 依赖中间件版本 |
支持多种算法 | 配置复杂度高 |
八、方案http://www.devze.com八:基因法 + Hash分片
核心思想
在ID中嵌入分片基因,直接定位分表。
Java实现代码
public class ShardIdGenerator { private static final int SHARD_COUNT = 8; // 8个分表 private static final int GEN_BITS = 3; // 3位基因(2^3=8) public long generateShardId(long baseId, int shardIndex) { // 基因掩码:0b00000111 long mask = (1 << GEN_BITS) - 1; // 清除低3位基因 long idwithoutGene = baseId & ~mask; // 设置新的基因 return idWithoutGene | (shardIndex & mask); } }
代码注解
- 基因提取:通过位运算定位分表索引
- 适用场景:Hash分片的分库分表系统
分表查询示例
long shardId = 1234567890123456789L; int shardIndex = (int)(shardId & ((1 << 3) - 1)); // 提取低3位 String tableName = "t_order_" + shardIndex;
优缺点分析
优点 | 缺点 |
---|---|
直接定位分表 | ID修改后需重新计算 |
无需额外组件 | 分片规则强依赖基因位 |
如何选择最适合你的方案?
方案 | 适用场景 | 推荐指数 |
---|---|---|
数据库自增+步长 | 小规模分表 | ⭐⭐⭐ |
UUID | 分布式系统 | ⭐⭐⭐⭐ |
Snowflake | 高并发系统 | ⭐⭐⭐⭐⭐ |
Redis | 高性能要求 | ⭐⭐⭐⭐ |
TinyID | 多业务场景 | ⭐⭐⭐⭐ |
COMB ID | 有序性优先 | ⭐⭐⭐ |
ShardingSphere | 中间件集成 | ⭐⭐⭐⭐ |
基因法 | Hash分片 | ⭐⭐⭐⭐ |
最后提醒:选择主键生成方案时,需综合考虑业务规模、性能需求和运维成本。记住:没有银弹方案,只有最合适的解决方案!
代码包结构
MainKeyGenerator/ ├── config/ │ └── shard.properties # 分片配置 ├── service/ │ ├── RedisIdService.java # Redis生成器 │ ├── SnowflakeService.java # Snowflake实现 │ └── TinyIdService.java # TinyID客户端 ├── model/ │ └── Order.java # 订单模型 └── util/ └── ShardUtil.java # 分片工具类
部署建议:
- 生产环境建议采用Snowflake或TinyID
- 高并发场景优先使用Redis集群
- 小规模系统可使用数据库自增步长
“主键ID是分库分表系统的命脉,选对方案,你的系统才能稳如泰山!”
以上就是MySQL分库分表后主键ID生成的八种方案的详细内容,更多关于MySQL主键ID生成方案的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论