MySQL分库分表主键重复解决方案:Snowflake雪花算法详解‌

1. 引言

随着业务数据量的快速增长,单库 MySQL 往往难以承受高并发和大数据存储压力。分库分表成为常见的数据库水平扩展方案:

  • 分库:将数据分散到多个数据库实例
  • 分表:将同一个数据库的数据分散到多张物理表

但是分库分表带来了一个新的问题:

如何保证全局主键唯一性?

在单表中我们可以直接用 AUTO_INCREMENT 自增 ID 作为主键,但在分库分表场景下:

  1. 每个表自增 ID 独立,容易产生重复
  2. 分布式系统需要全局唯一的主键标识

解决方案之一就是使用 Snowflake 雪花算法 生成全局唯一 ID。


2. 分库分表的主键重复问题

假设我们将用户表 user 分成 4 张表:

user_0, user_1, user_2, user_3

每张表用 MySQL 自增主键:

CREATE TABLE user_0 (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100)
);

如果每张表的自增 ID 都从 1 开始:

user_0.id: 1,2,3...
user_1.id: 1,2,3...
user_2.id: 1,2,3...
问题:全局范围内会出现大量重复 ID,无法唯一标识一条记录。

3. 分布式全局唯一 ID 生成方案

在分布式系统中,常见的全局唯一 ID 生成方案包括:

  1. UUID

    • 优点:简单,不依赖数据库
    • 缺点:长度长(128bit),无序,索引性能差
  2. 数据库号段(Hi/Lo)

    • 优点:自增,有序
    • 缺点:依赖数据库,扩展性一般
  3. 雪花算法(Snowflake)

    • 优点:高性能、本地生成、趋势递增、有序可读
    • 缺点:需要时钟正确性保证

4. Snowflake 雪花算法原理

Snowflake 是 Twitter 开源的分布式唯一 ID 生成算法,生成 64 位整型 ID(long)。

4.1 ID 结构

| 1bit 符号位 | 41bit 时间戳 | 10bit 机器ID | 12bit 自增序列 |

详细结构:

  1. 符号位 (1bit)

    • 永远为 0(保证正数)
  2. 时间戳 (41bit)

    • 单位毫秒
    • 可使用约 69 年(2^41 / (1000606024365))
  3. 机器ID (10bit)

    • 可支持 1024 个节点
    • 一般拆为 5bit数据中心ID + 5bit机器ID
  4. 序列号 (12bit)

    • 每毫秒最多生成 4096 个 ID

4.2 ID 组成图解

0 | 41bit timestamp | 5bit datacenter | 5bit worker | 12bit sequence

例如:

0  00000000000000000000000000000000000000000  
   00001 00001 000000000001

5. Java 实现 Snowflake 算法

public class SnowflakeIdGenerator {
    private final long workerId;        // 机器ID
    private final long datacenterId;    // 数据中心ID
    private long sequence = 0L;         // 毫秒内序列

    // 起始时间戳
    private final long twepoch = 1609459200000L; // 2021-01-01

    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long sequenceBits = 12L;

    private final long maxWorkerId = ~(-1L << workerIdBits);        // 31
    private final long maxDatacenterId = ~(-1L << datacenterIdBits);// 31
    private final long sequenceMask = ~(-1L << sequenceBits);       // 4095

    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("workerId out of range");
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId out of range");
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();

        // 时钟回拨处理
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards!");
        }

        if (lastTimestamp == timestamp) {
            // 同毫秒内递增
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                // 毫秒内序列用尽,等待下一毫秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << (5 + 5 + 12))
                | (datacenterId << (5 + 12))
                | (workerId << 12)
                | sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}

6. MySQL 分库分表应用方案

6.1 业务架构图

           +-----------------------+
           |   应用服务 (Java)      |
           +-----------------------+
                     |
                     v
      +-----------------------------+
      |  Snowflake ID 生成器 (本地) |
      +-----------------------------+
                     |
                     v
        +-------------------------+
        |  Sharding JDBC / MyCat  |
        +-------------------------+
            |        |       |
            v        v       v
         DB0.User DB1.User DB2.User

流程:

  1. 应用启动本地 Snowflake 生成器(分配 datacenterId 和 workerId)
  2. 插入数据时生成全局唯一 ID
  3. Sharding-JDBC 根据分片键路由到指定库表
  4. 全局主键不冲突

6.2 插入数据示例

long userId = snowflake.nextId();

jdbcTemplate.update("INSERT INTO user (id, name) VALUES (?, ?)", userId, "Alice");

6.3 优势

  • 本地生成,无中心化瓶颈
  • 趋势递增,索引性能好
  • 支持高并发:单机可达 \~400 万 ID/s

7. 实战优化与注意事项

  1. 时钟回拨问题

    • Snowflake 依赖时间戳,如果系统时间回拨,可能导致重复 ID
    • 解决:使用 NTP 同步时间,或加逻辑等待
  2. 机器 ID 分配

    • 可用 ZooKeeper / Etcd 分配 workerId
    • 或使用配置文件固定
  3. 高并发优化

    • 使用无锁 LongAdder 或分段锁提高吞吐
    • 结合 RingBuffer 做异步批量生成(如 Leaf Segment 模式)

8. 总结

在 MySQL 分库分表场景下:

  • 使用 MySQL 自增 ID 会产生主键冲突
  • UUID 太长且无序
  • Snowflake 雪花算法是最优解之一
最后修改于:2025年08月06日 21:20

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日