MySQL分庫(kù)分表后主鍵ID生成的八種方案
分庫(kù)分表后的主鍵ID沖突陷阱
當(dāng)你的MySQL數(shù)據(jù)庫(kù)因數(shù)據(jù)量或并發(fā)壓力進(jìn)行分庫(kù)分表后,主鍵ID重復(fù)會(huì)成為系統(tǒng)崩潰的導(dǎo)火索。假設(shè)你將訂單表拆分為10個(gè)分表,每個(gè)分表都從1開(kāi)始自增,最終會(huì)出現(xiàn)以下災(zāi)難性問(wèn)題:
- ID沖突:不同分表中的訂單ID可能完全相同
- 業(yè)務(wù)混亂:無(wú)法通過(guò)ID直接定位數(shù)據(jù)所在分表
- 分布式事務(wù)失敗:主鍵沖突導(dǎo)致寫入操作異常
本文將從底層原理出發(fā),結(jié)合真實(shí)業(yè)務(wù)代碼,深度解析8種主流主鍵ID生成方案,助你構(gòu)建穩(wěn)定可靠的分庫(kù)分表系統(tǒng)。
一、方案一:數(shù)據(jù)庫(kù)自增ID + 分段設(shè)置
核心思想
通過(guò)為每個(gè)分表配置不同的起始值和步長(zhǎng),確保ID全局唯一。
實(shí)現(xiàn)代碼
-- 分表1(t_order_0)配置 ALTER TABLE t_order_0 AUTO_INCREMENT = 1; -- 起始值1,步長(zhǎng)10 -- 分表2(t_order_1)配置 ALTER TABLE t_order_1 AUTO_INCREMENT = 11; -- 起始值11,步長(zhǎng)10 -- 分表3(t_order_2)配置 ALTER TABLE t_order_2 AUTO_INCREMENT = 21; -- 起始值21,步長(zhǎng)10
代碼注解
- 步長(zhǎng)設(shè)置:若總分表數(shù)為N,則每個(gè)分表步長(zhǎng)設(shè)為N
- 起始值計(jì)算:分表索引i的起始值為
i * N + 1 - 適用場(chǎng)景:分表數(shù)量固定的小規(guī)模系統(tǒng)
Java調(diào)用示例
// 根據(jù)訂單ID計(jì)算分表索引
int getTableIndex(Long orderId) {
return (int)(orderId % 10); // 假設(shè)分10個(gè)表
}
// 插入訂單
void insertOrder(Order order) {
int tableIndex = getTableIndex(order.getId());
String tableName = "t_order_" + tableIndex;
// 動(dòng)態(tài)拼接SQL
String sql = "INSERT INTO " + tableName + "(id, order_no) VALUES (?, ?)";
jdbcTemplate.update(sql, order.getId(), order.getOrderNo());
}
優(yōu)缺點(diǎn)分析
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 實(shí)現(xiàn)簡(jiǎn)單,無(wú)需額外組件 | 分表數(shù)量固定,擴(kuò)容困難 |
| 性能優(yōu)秀,直接利用數(shù)據(jù)庫(kù)特性 | 高并發(fā)下單表自增ID可能耗盡 |
| 存儲(chǔ)效率高,ID緊湊 | 需手動(dòng)維護(hù)分表配置 |
二、方案二:UUID全局唯一標(biāo)識(shí)符
核心思想
利用UUID的128位隨機(jī)性保證全局唯一性。
實(shí)現(xiàn)代碼
-- 創(chuàng)建訂單表(主鍵字段為UUID)
CREATE TABLE t_order (
id CHAR(36) PRIMARY KEY, -- UUID長(zhǎng)度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 - 存儲(chǔ)優(yōu)化:可壓縮為16字節(jié)二進(jìn)制存儲(chǔ)(Base128編碼)
性能優(yōu)化示例
// 壓縮UUID為16字節(jié)
public byte[] compressUuid(String uuid) {
return UUID.fromString(uuid).toString().getBytes(StandardCharsets.UTF_8);
}
優(yōu)缺點(diǎn)分析
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 完全隨機(jī),無(wú)依賴數(shù)據(jù)庫(kù) | 存儲(chǔ)空間大(36字節(jié)) |
| 適用于分布式系統(tǒng) | B+樹(shù)索引性能差(隨機(jī)寫入) |
| 無(wú)需協(xié)調(diào)生成ID | 無(wú)法直接定位分表 |
三、方案三:Snowflake算法(Twitter開(kāi)源)
核心思想
通過(guò)時(shí)間戳+機(jī)器ID+序列號(hào)生成64位有序ID。
Java實(shí)現(xiàn)代碼
public class SnowflakeIdGenerator {
private final long twepoch = 1288834974657L; // 起始時(shí)間戳(2010-11-04)
private final long workerIdBits = 5L; // 機(jī)器ID位數(shù)
private final long datacenterIdBits = 5L; // 數(shù)據(jù)中心ID位數(shù)
private final long sequenceBits = 12L; // 序列號(hào)位數(shù)
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("時(shí)鐘回?fù)? %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();
}
}
代碼注解
- 位運(yùn)算邏輯:
- 時(shí)間戳(41位) → 代表從起始時(shí)間到現(xiàn)在的毫秒數(shù)
- 數(shù)據(jù)中心ID(5位) → 支持32個(gè)數(shù)據(jù)中心
- 機(jī)器ID(5位) → 支持32個(gè)節(jié)點(diǎn)
- 序列號(hào)(12位) → 毫秒內(nèi)最多支持4096個(gè)ID
調(diào)用示例
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
long orderId = idGenerator.nextId();
System.out.println("生成的ID: " + orderId);
優(yōu)缺點(diǎn)分析
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 全局唯一且有序 | 依賴系統(tǒng)時(shí)間(時(shí)鐘回?fù)軉?wèn)題) |
| 支持分布式場(chǎng)景 | 需要預(yù)先分配機(jī)器ID |
| 存儲(chǔ)效率高(8字節(jié)) | 最大時(shí)間戳限制為69年 |
四、方案四:Redis原子自增
核心思想
利用Redis的INCR命令生成全局唯一ID。
Redis配置
# 啟動(dòng)Redis redis-server # 設(shè)置初始值 SET order_id 1000
Java調(diào)用示例
import redis.clients.jedis.Jedis;
public class RedisIdGenerator {
private Jedis jedis = new Jedis("localhost");
public long generateId() {
return jedis.incr("order_id"); // 原子自增
}
}
代碼注解
- 高可用保障:建議使用Redis集群模式
- 分片策略:可通過(guò)不同Key區(qū)分業(yè)務(wù)類型(如
order_id、user_id)
集群模式優(yōu)化
// Redis Cluster客戶端配置
JedisCluster jedisCluster = new JedisCluster(new HostAndPort("192.168.1.101", 6379),
new HostAndPort("192.168.1.102", 6379));
優(yōu)缺點(diǎn)分析
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 高性能(每秒百萬(wàn)級(jí)QPS) | 依賴Redis服務(wù)穩(wěn)定性 |
| 支持分布式場(chǎng)景 | 單點(diǎn)故障風(fēng)險(xiǎn)(需集群) |
| 簡(jiǎn)單易用 | 需處理網(wǎng)絡(luò)延遲 |
五、方案五:第三方ID生成服務(wù)
核心思想
使用獨(dú)立服務(wù)(如滴滴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原理:基于數(shù)據(jù)庫(kù)的Sequence表生成ID
- 多租戶支持:通過(guò)
businessType區(qū)分不同業(yè)務(wù)
TinyID服務(wù)端配置
CREATE TABLE tinyid (
id BIGINT PRIMARY KEY,
business_type VARCHAR(50),
max_id BIGINT,
step INT,
version INT
);
優(yōu)缺點(diǎn)分析
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 支持多業(yè)務(wù)類型 | 需維護(hù)獨(dú)立服務(wù) |
| 高可用性(支持集群) | 依賴網(wǎng)絡(luò)通信 |
| 靈活配置步長(zhǎng) | 學(xué)習(xí)成本較高 |
六、方案六:COMB ID(組合ID)
核心思想
將時(shí)間戳與隨機(jī)數(shù)組合,生成有序UUID。
Java實(shí)現(xiàn)代碼
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字節(jié)為時(shí)間戳
long timestamp = Instant.now().toEpochMilli();
for (int i = 0; i < 6; i++) {
uuid[i] = (byte)(timestamp >> (8 * (5 - i)));
}
// 后10字節(jié)為隨機(jī)數(shù)
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();
}
}
代碼注解
- 時(shí)間戳部分:確保ID有序性
- 隨機(jī)部分:避免重復(fù)
優(yōu)缺點(diǎn)分析
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 兼具有序性和隨機(jī)性 | 實(shí)現(xiàn)復(fù)雜度高 |
| 降低B+樹(shù)索引碎片 | 依賴時(shí)間同步 |
七、方案七:數(shù)據(jù)庫(kù)中間件內(nèi)置策略
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
代碼注解
- 內(nèi)置算法:支持UUID和Snowflake
- 自定義擴(kuò)展:可通過(guò)接口實(shí)現(xiàn)自定義生成器
自定義生成器示例
public class CustomKeyGenerator implements KeyGenerator {
@Override
public Comparable<?> generateKey() {
return new SnowflakeIdGenerator(1, 1).nextId();
}
}
優(yōu)缺點(diǎn)分析
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 無(wú)縫集成ShardingSphere | 依賴中間件版本 |
| 支持多種算法 | 配置復(fù)雜度高 |
八、方案八:基因法 + Hash分片
核心思想
在ID中嵌入分片基因,直接定位分表。
Java實(shí)現(xiàn)代碼
public class ShardIdGenerator {
private static final int SHARD_COUNT = 8; // 8個(gè)分表
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;
// 設(shè)置新的基因
return idWithoutGene | (shardIndex & mask);
}
}
代碼注解
- 基因提取:通過(guò)位運(yùn)算定位分表索引
- 適用場(chǎng)景:Hash分片的分庫(kù)分表系統(tǒng)
分表查詢示例
long shardId = 1234567890123456789L; int shardIndex = (int)(shardId & ((1 << 3) - 1)); // 提取低3位 String tableName = "t_order_" + shardIndex;
優(yōu)缺點(diǎn)分析
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 直接定位分表 | ID修改后需重新計(jì)算 |
| 無(wú)需額外組件 | 分片規(guī)則強(qiáng)依賴基因位 |
如何選擇最適合你的方案?
| 方案 | 適用場(chǎng)景 | 推薦指數(shù) |
|---|---|---|
| 數(shù)據(jù)庫(kù)自增+步長(zhǎng) | 小規(guī)模分表 | ??? |
| UUID | 分布式系統(tǒng) | ???? |
| Snowflake | 高并發(fā)系統(tǒng) | ????? |
| Redis | 高性能要求 | ???? |
| TinyID | 多業(yè)務(wù)場(chǎng)景 | ???? |
| COMB ID | 有序性優(yōu)先 | ??? |
| ShardingSphere | 中間件集成 | ???? |
| 基因法 | Hash分片 | ???? |
最后提醒:選擇主鍵生成方案時(shí),需綜合考慮業(yè)務(wù)規(guī)模、性能需求和運(yùn)維成本。記住:沒(méi)有銀彈方案,只有最合適的解決方案!
代碼包結(jié)構(gòu)
MainKeyGenerator/
├── config/
│ └── shard.properties # 分片配置
├── service/
│ ├── RedisIdService.java # Redis生成器
│ ├── SnowflakeService.java # Snowflake實(shí)現(xiàn)
│ └── TinyIdService.java # TinyID客戶端
├── model/
│ └── Order.java # 訂單模型
└── util/
└── ShardUtil.java # 分片工具類
部署建議:
- 生產(chǎn)環(huán)境建議采用Snowflake或TinyID
- 高并發(fā)場(chǎng)景優(yōu)先使用Redis集群
- 小規(guī)模系統(tǒng)可使用數(shù)據(jù)庫(kù)自增步長(zhǎng)
“主鍵ID是分庫(kù)分表系統(tǒng)的命脈,選對(duì)方案,你的系統(tǒng)才能穩(wěn)如泰山!”
以上就是MySQL分庫(kù)分表后主鍵ID生成的八種方案的詳細(xì)內(nèi)容,更多關(guān)于MySQL主鍵ID生成方案的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
navicat連接mysql報(bào)錯(cuò)1251的解決方法
這篇文章主要為大家詳細(xì)介紹了navicat連接mysql報(bào)錯(cuò)1251的解決方法,文中解決方法介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
MySQL中Nested-Loop Join算法小結(jié)
數(shù)據(jù)庫(kù)中JOIN操作的實(shí)現(xiàn)主要有三種:嵌套循環(huán)連接(Nested Loop Join),歸并連接(Merge Join)和散列連接或者哈稀連接(Hash Join)。其中嵌套循環(huán)連接又視情況又有兩種變形:塊嵌套循環(huán)連接和索引嵌套循環(huán)連接。2015-12-12
mysql 導(dǎo)入導(dǎo)出數(shù)據(jù)庫(kù)、數(shù)據(jù)表的方法
這篇文章主要介紹了mysql 導(dǎo)入導(dǎo)出數(shù)據(jù)庫(kù)、數(shù)據(jù)表的方法,有需要的朋友可以參考一下2013-11-11
mysql如何通過(guò)my.ini更改默認(rèn)字符集
這篇文章主要介紹了mysql如何通過(guò)my.ini更改默認(rèn)字符集問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12

