当前位置: 首页 > news >正文

雪花算法生成的主键存在哪些问题,为什么不能使用自增ID或者UUID做MySQL的主键

MySQL 分布式架构中的主键选择:自增ID、UUID与雪花算法

为什么MySQL分布式架构中不能使用自增主键?

在分布式架构中,自增主键存在以下问题:

  1. 主键冲突风险:多个数据库实例同时生成自增主键会导致ID重复
  2. 分片不均匀
    • 采用范围分片时会出现"尾部热点"现象,压力集中在某个分片
    • 无法实现负载均衡,新数据只能写入当前分片
  3. 数据合并困难:合并多个数据库时,自增主键会重复
  4. 性能瓶颈:自增锁在高并发场景下会成为性能瓶颈
  5. 安全性问题:自增ID容易被猜测,可能被用于恶意爬取数据

UUID作为主键的优缺点

优点

全局唯一性:几乎可以保证全球范围内的唯一性
分布式友好:无需协调即可在不同节点生成
安全性:随机生成的UUID难以被猜测

缺点

存储空间大:16字节(128位),是自增ID(通常4字节)的4倍
索引性能差
• 无序插入导致B+树频繁分裂和平衡
• 增加索引大小,降低缓存命中率
可读性差:长字符串形式不利于人工识别和调试
碎片化问题:随机插入导致磁盘碎片化

MySQL 8.0优化:可使用UUID_TO_BIN函数将UUID转换为16字节二进制并排序,性能接近自增ID

雪花算法(SnowFlake)详解

原理

雪花算法生成64位长整型ID,结构如下:

0 | 0000000 00000000 00000000 00000000 00000000 | 00000 | 00000 | 00000000 0000
  1. 1位符号位:固定为0(正数)
  2. 41位时间戳:毫秒级时间,可用69年(从1970算起)
  3. 10位机器标识
    • 5位数据中心ID(32个可能值)
    • 5位工作机器ID(32个可能值)
  4. 12位序列号:同一毫秒内的计数器(4096个值/ms/机器)

优势

  1. 全局唯一:通过时间戳+机器ID+序列号组合保证
  2. 有序递增:基于时间戳,有利于索引和排序
  3. 高性能:本地生成,每秒可生成数百万ID
  4. 不依赖第三方:算法简单,内存中完成
  5. 分布式友好:支持最多1024个节点(10位机器标识)

不足与解决方案

  1. 时钟回拨问题
    问题:服务器时间被调回导致重复ID
    解决方案
    ◦ 直接抛出异常,停止服务
    ◦ 记录最近时间戳,回拨时等待
    ◦ 使用扩展位记录时钟序列(3位时钟序列+7位机器ID)
    ◦ 采用Leaf、UidGenerator等改进方案

  2. 机器ID限制
    问题:10位仅支持1024个节点
    解决方案
    ◦ 预分配ID(适合固定节点)
    ◦ 动态分配(使用Redis/Zookeeper存储ID)
    ◦ 扩展位数(牺牲部分时间戳或序列号位)

  3. 时间戳耗尽
    问题:41位时间戳约69年后耗尽
    解决方案:调整起始时间(如使用系统上线时间而非1970)

代码示例(Java)

public class SnowflakeIdGenerator {
    private final long twepoch = 1577836800000L; // 自定义起始时间(2020-01-01)
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final long sequenceBits = 12L;
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        // 参数校验
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();
        // 处理时钟回拨
        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) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }
    // 其他辅助方法...
}

总结对比

特性自增IDUUID雪花算法
唯一性单机唯一全局唯一全局唯一
有序性严格有序完全无序时间有序
存储空间4-8字节16字节8字节
分布式支持不支持支持支持
生成方式数据库生成应用生成应用生成
性能影响自增锁瓶颈索引分裂时钟依赖
适用场景单机MySQL简单分布式系统高并发分布式系统

推荐选择
• 单机系统:自增ID
• 简单分布式系统:MySQL 8.0的有序UUID
• 高并发分布式系统:雪花算法或其改进版(如Leaf)

相关文章:

  • git 对比两种优化方法的性能
  • MySQL主从复制(二)
  • Go语言入门指南:从语法基础到核心特性解析
  • 【C++】mapset使用与实战 OJ题
  • ABAP RANGE表 OPTION 运算符 SIGN
  • 无人机数据链技术及运行方式详解!
  • python生成并绘制各种类型声音噪声
  • xfreerdp 的使用
  • Spring的 init-method, @PostConstruct, InitializingBean 对比
  • 【鸿蒙5.0】两个数组,点击事件两个数组数据进行了双向数据交换,双向绑定的原理是什么?
  • JVM——模型分析、回收机制
  • 学透Spring Boot — 007. 加载外部配置
  • 【蓝桥杯14天冲刺课题单】Day 8
  • MQTT 服务器(emqx)搭建及使用(二)
  • 【原创】使用Golang和wails开发桌面程序初探
  • 基于CT成像的肿瘤图像分类:方法与实现
  • 多级限流防止 Redis 被打爆
  • Mysql-DQL
  • Docker学习--本地镜像管理相关命令--docker rmi 命令
  • bert自然语言处理框架
  • 新型算法助力听障人士听得更清晰
  • 圆桌|如何应对特朗普政府的关税霸凌?一种联合国视角的思考
  • “女乘客遭顺风车深夜丢高速服务区”续:滴滴永久封禁两名涉事司机账号
  • 庄语乐︱宋代历史是被“塑造”出来的吗?
  • 青海省林业和草原局副局长旦增主动投案,正接受审查调查
  • 五一假期如何躺赚利息?来看国债逆回购操作攻略