开发者

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

开发者 https://www.devze.com 2025-08-09 08:58 出处:网络 作者: 墨夶
目录分库分表后的主键ID冲突陷阱一、方案一:数据库自增ID + 分段设置核心思想实现代码代码注解Java调用示例优缺点分析二、方案二:UUID全局唯一标识符核心思想实现代码Java生成UUID代码注解性能优化示例优缺点分析三
目录
  • 分库分表后的主键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_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; 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         # 分片工具类
      

      部署建议

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

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

      以上就是MySQL分库分表后主键ID生成的八种方案的详细内容,更多关于MySQL主键ID生成方案的资料请关注编程客栈(www.devze.com)其它相关文章!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号