当前位置: 代码网 > it编程>数据库>Mysql > MySQL分库分表后主键ID生成的八种方案

MySQL分库分表后主键ID生成的八种方案

2025年08月08日 Mysql 我要评论
分库分表后的主键id冲突陷阱当你的mysql数据库因数据量或并发压力进行分库分表后,主键id重复会成为系统崩溃的导火索。假设你将订单表拆分为10个分表,每个分表都从1开始自增,最终会出现以下灾难性问题

分库分表后的主键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 " + tablename + "(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位) → 支持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_iduser_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; i++) {
            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依赖中间件版本
支持多种算法配置复杂度高

八、方案八:基因法 + 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         # 分片工具类

部署建议

  • 生产环境建议采用snowflaketinyid
  • 高并发场景优先使用redis集群
  • 小规模系统可使用数据库自增步长

“主键id是分库分表系统的命脉,选对方案,你的系统才能稳如泰山!”

以上就是mysql分库分表后主键id生成的八种方案的详细内容,更多关于mysql主键id生成方案的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com