MySQL分庫分表后主鍵ID生成的八種方案
分庫分表后的主鍵ID沖突陷阱
當你的MySQL數(shù)據(jù)庫因數(shù)據(jù)量或并發(fā)壓力進行分庫分表后,主鍵ID重復(fù)會成為系統(tǒng)崩潰的導火索。假設(shè)你將訂單表拆分為10個分表,每個分表都從1開始自增,最終會出現(xiàn)以下災(zāi)難性問題:
- ID沖突:不同分表中的訂單ID可能完全相同
- 業(yè)務(wù)混亂:無法通過ID直接定位數(shù)據(jù)所在分表
- 分布式事務(wù)失敗:主鍵沖突導致寫入操作異常
本文將從底層原理出發(fā),結(jié)合真實業(yè)務(wù)代碼,深度解析8種主流主鍵ID生成方案,助你構(gòu)建穩(wěn)定可靠的分庫分表系統(tǒng)。
一、方案一:數(shù)據(jù)庫自增ID + 分段設(shè)置
核心思想
通過為每個分表配置不同的起始值和步長,確保ID全局唯一。
實現(xiàn)代碼
-- 分表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
代碼注解
- 步長設(shè)置:若總分表數(shù)為N,則每個分表步長設(shè)為N
- 起始值計算:分表索引i的起始值為
i * N + 1 - 適用場景:分表數(shù)量固定的小規(guī)模系統(tǒng)
Java調(diào)用示例
// 根據(jù)訂單ID計算分表索引
int getTableIndex(Long orderId) {
return (int)(orderId % 10); // 假設(shè)分10個表
}
// 插入訂單
void insertOrder(Order order) {
int tableIndex = getTableIndex(order.getId());
String tableName = "t_order_" + tableIndex;
// 動態(tài)拼接SQL
String sql = "INSERT INTO " + tableName + "(id, order_no) VALUES (?, ?)";
jdbcTemplate.update(sql, order.getId(), order.getOrderNo());
}
優(yōu)缺點分析
| 優(yōu)點 | 缺點 |
|---|---|
| 實現(xiàn)簡單,無需額外組件 | 分表數(shù)量固定,擴容困難 |
| 性能優(yōu)秀,直接利用數(shù)據(jù)庫特性 | 高并發(fā)下單表自增ID可能耗盡 |
| 存儲效率高,ID緊湊 | 需手動維護分表配置 |
二、方案二:UUID全局唯一標識符
核心思想
利用UUID的128位隨機性保證全局唯一性。
實現(xiàn)代碼
-- 創(chuàng)建訂單表(主鍵字段為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 - 存儲優(yōu)化:可壓縮為16字節(jié)二進制存儲(Base128編碼)
性能優(yōu)化示例
// 壓縮UUID為16字節(jié)
public byte[] compressUuid(String uuid) {
return UUID.fromString(uuid).toString().getBytes(StandardCharsets.UTF_8);
}
優(yōu)缺點分析
| 優(yōu)點 | 缺點 |
|---|---|
| 完全隨機,無依賴數(shù)據(jù)庫 | 存儲空間大(36字節(jié)) |
| 適用于分布式系統(tǒng) | B+樹索引性能差(隨機寫入) |
| 無需協(xié)調(diào)生成ID | 無法直接定位分表 |
三、方案三:Snowflake算法(Twitter開源)
核心思想
通過時間戳+機器ID+序列號生成64位有序ID。
Java實現(xiàn)代碼
public class SnowflakeIdGenerator {
private final long twepoch = 1288834974657L; // 起始時間戳(2010-11-04)
private final long workerIdBits = 5L; // 機器ID位數(shù)
private final long datacenterIdBits = 5L; // 數(shù)據(jù)中心ID位數(shù)
private final long sequenceBits = 12L; // 序列號位數(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("時鐘回撥: %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位) → 代表從起始時間到現(xiàn)在的毫秒數(shù)
- 數(shù)據(jù)中心ID(5位) → 支持32個數(shù)據(jù)中心
- 機器ID(5位) → 支持32個節(jié)點
- 序列號(12位) → 毫秒內(nèi)最多支持4096個ID
調(diào)用示例
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
long orderId = idGenerator.nextId();
System.out.println("生成的ID: " + orderId);
優(yōu)缺點分析
| 優(yōu)點 | 缺點 |
|---|---|
| 全局唯一且有序 | 依賴系統(tǒng)時間(時鐘回撥問題) |
| 支持分布式場景 | 需要預(yù)先分配機器ID |
| 存儲效率高(8字節(jié)) | 最大時間戳限制為69年 |
四、方案四:Redis原子自增
核心思想
利用Redis的INCR命令生成全局唯一ID。
Redis配置
# 啟動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集群模式
- 分片策略:可通過不同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)缺點分析
| 優(yōu)點 | 缺點 |
|---|---|
| 高性能(每秒百萬級QPS) | 依賴Redis服務(wù)穩(wěn)定性 |
| 支持分布式場景 | 單點故障風險(需集群) |
| 簡單易用 | 需處理網(wǎng)絡(luò)延遲 |
五、方案五:第三方ID生成服務(wù)
核心思想
使用獨立服務(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ù)庫的Sequence表生成ID
- 多租戶支持:通過
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)缺點分析
| 優(yōu)點 | 缺點 |
|---|---|
| 支持多業(yè)務(wù)類型 | 需維護獨立服務(wù) |
| 高可用性(支持集群) | 依賴網(wǎng)絡(luò)通信 |
| 靈活配置步長 | 學習成本較高 |
六、方案六:COMB ID(組合ID)
核心思想
將時間戳與隨機數(shù)組合,生成有序UUID。
Java實現(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é)為時間戳
long timestamp = Instant.now().toEpochMilli();
for (int i = 0; i < 6; i++) {
uuid[i] = (byte)(timestamp >> (8 * (5 - i)));
}
// 后10字節(jié)為隨機數(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();
}
}
代碼注解
- 時間戳部分:確保ID有序性
- 隨機部分:避免重復(fù)
優(yōu)缺點分析
| 優(yōu)點 | 缺點 |
|---|---|
| 兼具有序性和隨機性 | 實現(xiàn)復(fù)雜度高 |
| 降低B+樹索引碎片 | 依賴時間同步 |
七、方案七:數(shù)據(jù)庫中間件內(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
- 自定義擴展:可通過接口實現(xiàn)自定義生成器
自定義生成器示例
public class CustomKeyGenerator implements KeyGenerator {
@Override
public Comparable<?> generateKey() {
return new SnowflakeIdGenerator(1, 1).nextId();
}
}
優(yōu)缺點分析
| 優(yōu)點 | 缺點 |
|---|---|
| 無縫集成ShardingSphere | 依賴中間件版本 |
| 支持多種算法 | 配置復(fù)雜度高 |
八、方案八:基因法 + Hash分片
核心思想
在ID中嵌入分片基因,直接定位分表。
Java實現(xiàn)代碼
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;
// 設(shè)置新的基因
return idWithoutGene | (shardIndex & mask);
}
}
代碼注解
- 基因提取:通過位運算定位分表索引
- 適用場景:Hash分片的分庫分表系統(tǒng)
分表查詢示例
long shardId = 1234567890123456789L; int shardIndex = (int)(shardId & ((1 << 3) - 1)); // 提取低3位 String tableName = "t_order_" + shardIndex;
優(yōu)缺點分析
| 優(yōu)點 | 缺點 |
|---|---|
| 直接定位分表 | ID修改后需重新計算 |
| 無需額外組件 | 分片規(guī)則強依賴基因位 |
如何選擇最適合你的方案?
| 方案 | 適用場景 | 推薦指數(shù) |
|---|---|---|
| 數(shù)據(jù)庫自增+步長 | 小規(guī)模分表 | ??? |
| UUID | 分布式系統(tǒng) | ???? |
| Snowflake | 高并發(fā)系統(tǒng) | ????? |
| Redis | 高性能要求 | ???? |
| TinyID | 多業(yè)務(wù)場景 | ???? |
| COMB ID | 有序性優(yōu)先 | ??? |
| ShardingSphere | 中間件集成 | ???? |
| 基因法 | Hash分片 | ???? |
最后提醒:選擇主鍵生成方案時,需綜合考慮業(yè)務(wù)規(guī)模、性能需求和運維成本。記住:沒有銀彈方案,只有最合適的解決方案!
代碼包結(jié)構(gòu)
MainKeyGenerator/
├── config/
│ └── shard.properties # 分片配置
├── service/
│ ├── RedisIdService.java # Redis生成器
│ ├── SnowflakeService.java # Snowflake實現(xiàn)
│ └── TinyIdService.java # TinyID客戶端
├── model/
│ └── Order.java # 訂單模型
└── util/
└── ShardUtil.java # 分片工具類
部署建議:
- 生產(chǎn)環(huán)境建議采用Snowflake或TinyID
- 高并發(fā)場景優(yōu)先使用Redis集群
- 小規(guī)模系統(tǒng)可使用數(shù)據(jù)庫自增步長
“主鍵ID是分庫分表系統(tǒng)的命脈,選對方案,你的系統(tǒng)才能穩(wěn)如泰山!”
以上就是MySQL分庫分表后主鍵ID生成的八種方案的詳細內(nèi)容,更多關(guān)于MySQL主鍵ID生成方案的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Windows server 2008 r2上安裝MySQL5.7.10步驟
這篇文章主要介紹了Windows server 2008 r2上安裝MySQL5.7.10的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01
Mysql數(shù)據(jù)庫表中為什么有索引卻沒有提高查詢速度
你有沒有想起過為什么明明再數(shù)據(jù)庫中有索引,但是查詢速度卻并沒有希望的那樣快?本篇文章將帶給你答案,跟小編一起看看吧2022-02-02
MySQL中設(shè)置服務(wù)器級別的默認排序規(guī)則的方法
collation_server?是一個系統(tǒng)變量,它定義了服務(wù)器級別的默認排序規(guī)則,本文主要介紹了MySQL中設(shè)置服務(wù)器級別的默認排序規(guī)則的方法,具有一定的參考價值,感興趣的可以了解一下2024-08-08
centos7.4系統(tǒng)中yum源安裝mysql 5.6
本文給大家介紹的是如何在centos7.4系統(tǒng)中通過yum源安裝MySQL 5.6數(shù)據(jù)庫,CentOS7默認數(shù)據(jù)庫是mariadb, 但是 好多用的都是mysql ,但是CentOS7的yum源中默認好像是沒有mysql的,今天我們就來看看具體如何操作2018-09-09

