Java-111 深入浅出 MySQL 分布式主键策略:UUID、SnowFlake、COMB、Redis、数据库ID表优劣全对比

发布于:2025-08-31 ⋅ 阅读:(20) ⋅ 点赞:(0)

点一下关注吧!!!非常感谢!!持续更新!!!

🚀 AI篇持续更新中!(长期更新)

AI炼丹日志-31- 千呼万唤始出来 GPT-5 发布!“快的模型 + 深度思考模型 + 实时路由”,持续打造实用AI工具指南!📐🤖

💻 Java篇正式开启!(300篇)

目前2025年08月18日更新到:
Java-100 深入浅出 MySQL事务隔离级别:读未提交、已提交、可重复读与串行化
MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务正在更新!深入浅出助你打牢基础!

📊 大数据板块已完成多项干货更新(300篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!
大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解

请添加图片描述

主键策略

在很多小项目中,我们往往直接使用数据库自增特性来生成主键ID,这样确实比较简单,而在分库分表的环境中,不能再借助数据库自增长特性直接生成,否则会造成不同数据表主键重复。

UUID(通用唯一识别码)

基本概念

UUID(Universally Unique Identifier)是一种128位(16字节)的数字标识符,用于在分布式系统中唯一地标识信息。其标准格式由32个十六进制数字组成,以连字符"-"分隔为5组,呈现为8-4-4-4-12的形式,总长度为36个字符(例如:550e8400-e29b-41d4-a716-446655440000)。

生成机制

UUID的生成通常结合多种系统信息以确保唯一性:

  1. 网络硬件信息(如MAC地址)
  2. 高精度时间戳(纳秒级)
  3. 硬件芯片ID
  4. 随机数生成器
  5. 命名空间(在特定版本中)

常见的版本包括:

  • Version 1:基于时间戳和MAC地址
  • Version 4:基于随机数生成
  • Version 5:基于命名空间和散列值

数据库应用特点

优势
  1. 分布式生成:无需中央服务器协调,各节点可独立生成
  2. 唯一性保证:在理论上有极低的重复概率(约10^38分之一)
  3. 无网络开销:本地生成不需要网络请求
  4. 安全性:不暴露业务信息(相比自增ID)
劣势
  1. 存储开销:占用16字节,是BIGINT的两倍
  2. 索引效率:在InnoDB引擎中,由于:
    • 无序性导致B+树频繁分裂重组
    • 二级索引需要存储完整的主键值
    • 增大了内存占用和IO操作
  3. 可读性差:人类难以直观记忆和识别

InnoDB引擎下的特殊影响

  1. 聚集索引问题:InnoDB使用主键作为聚簇索引,UUID的无序插入会导致:

    • 页分裂频率增加
    • 数据存储碎片化
    • 缓存命中率下降
  2. 二级索引膨胀:每个二级索引都包含主键值,16字节的UUID会导致:

    • 索引文件体积增大
    • 内存缓冲池能缓存的索引数量减少
    • 范围查询时需要加载更多数据页

优化方案

对于必须使用UUID的场景:

  1. 使用有序UUID变种(如COMB UUID)
  2. 将UUID转换为二进制(16)存储
  3. 建立自增ID作为聚簇索引,用UUID作为业务键
  4. 考虑使用UUID的短哈希版本(需评估碰撞概率)

应用场景推荐

适合使用UUID的情况:

  • 需要离线生成的分布式系统
  • 需要提前知道主键值的业务
  • 需要隐藏数据规模的场景
  • 多系统数据合并的场景

COMB(UUID变种)详解

基本概念

COMB(combine)型是数据库领域特有的一种设计思想,它是一种改进型的GUID/UUID实现方式。这种设计通过将传统GUID/UUID与系统时间信息进行组合,显著提升了数据库索引和检索性能。

技术背景

在标准数据库中并不存在原生的COMB数据类型,这个概念最早由Jimmy Nilsson在其技术文章《The Cost of GUIDs as Primary Keys》中提出并详细阐述。该文章深入分析了传统GUID作为主键的性能问题及其解决方案。

设计原理

COMB的设计基于以下技术考量:

  1. 传统GUID/UUID是完全随机的128位标识符
  2. 这种随机性导致数据库索引出现严重的碎片化问题
  3. 数据插入时的随机分布导致索引效率低下,影响系统整体性能

具体实现方案

COMB采用分段组合的方式重构GUID:

  1. 保留部分:保持GUID前10个字节(80位)不变,确保唯一性
  2. 时间部分:使用后6个字节(48位)存储GUID生成的时间戳(DateTime)
    • 精确到毫秒级时间信息
    • 时间戳采用大端序存储

性能优势

这种组合方式带来显著优势:

  1. 保持唯一性:前10个字节仍保证全局唯一
  2. 增加有序性:时间戳使新生成的ID呈现递增趋势
  3. 索引优化
    • 减少索引碎片
    • 提高范围查询效率
    • 优化数据页填充率

典型应用场景

  1. 分布式数据库主键设计
  2. 高并发订单系统
  3. 需要频繁插入的日志系统
  4. 大型电商平台的商品ID生成

与其他方案的对比

特性 标准UUID COMB
唯一性 保证 保证
有序性 时间有序
索引效率 较高
存储空间 16字节 16字节
生成复杂度 简单 中等

实现示例(伪代码)

Guid GenerateCombGuid()
{
    byte[] guidBytes = Guid.NewGuid().ToByteArray();
    DateTime now = DateTime.UtcNow;
    
    // 将时间信息写入后6字节
    byte[] timeBytes = BitConverter.GetBytes(now.Ticks);
    Array.Copy(timeBytes, 0, guidBytes, 10, 6);
    
    return new Guid(guidBytes);
}

注意事项

  1. 时间部分精度需根据业务需求调整
  2. 在极高并发环境下仍需考虑冲突问题
  3. 跨时区系统需要统一使用UTC时间
  4. 6字节时间戳可表示约8925年的时间范围

SnowFlake 分布式ID生成算法

在分布式系统中,我们经常需要一种能够全局唯一且按时间有序的ID生成方案。SnowFlake正是Twitter为解决这一问题而开源的分布式ID生成算法,它生成的ID是一个64位的long型整数。

数据结构解析

SnowFlake的64位ID由以下部分组成:

  1. 符号位(1bit):始终为0,保证生成的ID为正数

  2. 时间戳部分(41bit)

    • 记录生成ID的时间戳(毫秒级)
    • 41位可以表示的时间跨度约为69年(2^41/1000/60/60/24/365)
    • 通常从系统上线时间开始计算,例如2020-01-01 00:00:00
  3. 工作机器ID(10bit)

    • 高5位表示数据中心ID(最大支持32个数据中心)
    • 低5位表示机器ID(每个数据中心最大支持32台机器)
    • 这种设计可以支持最多1024台机器(32*32)
  4. 序列号(12bit)

    • 同一毫秒内产生的不同ID的序列号
    • 12位支持每个节点每毫秒产生4096个ID(2^12)

工作流程

  1. 当收到ID生成请求时,首先获取当前时间戳
  2. 如果当前时间戳小于上次生成ID的时间戳,说明系统时钟回拨,需要抛出异常
  3. 如果是同一毫秒内的请求,则递增序列号
  4. 如果序列号溢出,则等待至下一毫秒
  5. 最后将各部分数值通过位运算拼接成最终的64位ID

应用场景

  1. 分布式系统:作为全局唯一的事务ID
  2. 数据库主键:替代自增ID,避免分库分表时的ID冲突
  3. 消息队列:作为消息的唯一标识
  4. 日志追踪:作为请求链路的追踪ID

优势与限制

优势

  • ID自增趋势,利于数据库索引
  • 不依赖第三方服务,本地生成
  • 高性能,单机每秒可生成数百万ID

限制

  • 依赖系统时钟,时钟回拨会导致ID重复
  • 工作机器ID需要提前配置,不利于动态扩容

实现示例(伪代码)

public class SnowFlake {
    private long datacenterId;  // 数据中心ID
    private long workerId;      // 机器ID
    private long sequence = 0L; // 序列号
    private long lastTimestamp = -1L; // 上次时间戳
    
    public synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("时钟回拨异常");
        }
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        return ((timestamp - epoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }
}

如下图所示:

在这里插入图片描述

● 符号位:固定位0,二进制表示最高位是符号位,0代表正数,1代表负数
● 时间戳:41个二进制数用来记录时间戳,表示某一个毫秒(毫秒级)
● 机器ID:代表当前算法运行机器的ID
● 序列号:12位,用来记录某个机器同一个毫秒内产生的不同序列号,代表同一个机器同一个毫秒可以产生ID序号

SnowFlake 生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID重复,并且效率较高。经过测试SnowFlake每秒能够产生26万个ID。缺点是强依赖机器时钟,如果多台机器环境时钟没同步,或者时钟回拨,会导致发号重复或者服务会处于不可用的状态,
因此一些互联网公司也基于上述的方案做了封装,例如百度的uidgenerator(基于SnowFlake)和美团leaf(基于数据库和SnowFlake)等。

数据库ID表(分布式ID生成方案)

核心原理

通过独立维护一个专门用于生成全局唯一ID的数据库表,利用MySQL的自增ID特性实现分布式环境下的ID生成。

实现方案详解

  1. 独立ID库建设

    • 单独创建一个MySQL数据库实例(如命名为id_generator_db
    • 在该库中创建专门用于ID生成的表(如global_id_table
    • 表结构设计示例:
      CREATE TABLE global_id_table (
        id bigint NOT NULL AUTO_INCREMENT,
        stub char(1) NOT NULL DEFAULT '',
        PRIMARY KEY (id),
        UNIQUE KEY stub (stub)
      ) ENGINE=InnoDB;
      
  2. ID生成流程

    • 业务系统需要ID时,执行以下SQL:
      REPLACE INTO global_id_table (stub) VALUES ('a');
      SELECT LAST_INSERT_ID();
      
    • 获取到ID后即可用于业务表的插入操作
  3. 分表场景应用

    • 以A表分表为例:
      • 先向global_id_table获取全局ID
      • 根据分表规则(如ID取模)决定插入A1还是A2表
      • 示例代码:
        // 获取全局ID
        long id = getIdFromGlobalTable();
        
        // 确定分表
        String tableName = "A" + (id % 2 + 1); // A1或A2
        
        // 插入业务表
        insertIntoTable(tableName, id, ...);
        

优化与注意事项

  1. 性能优化

    • 可使用连接池管理ID库连接
    • 批量获取ID:通过设置auto_increment_increment参数批量分配ID段
  2. 高可用方案

    • 部署主从架构,避免单点故障
    • 可考虑多机房部署ID生成服务
  3. 使用限制

    • 单库吞吐量有限(约1-2万QPS)
    • 跨机房调用可能产生网络延迟
    • 需注意自增ID的溢出问题(使用bigint类型)

替代方案对比

当单库性能不足时,可考虑:

  1. 分库分表:将ID表水平拆分到多个库
  2. 号段模式:每次获取一个ID范围段
  3. Snowflake算法:分布式ID生成算法

典型应用场景

  1. 电商订单系统
  2. 社交网络中的动态ID生成
  3. 物流系统的运单编号
  4. 金融交易流水号生成

通过这种方案,可以在分布式系统中保证ID的全局唯一性,同时维持较好的顺序性,便于分库分表场景下的数据路由。

例如,下面 DISTRIBUTE_ID就我们创建要负责ID生成的表,结构如下:

CREATE TABLE DISTRIBUTE_ID (
id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '主键',
createtime datetime DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

当分布式集群环境中哪个应用需要获取一个全局唯一的分布式ID的时候,就可以使用代码连接这个数据库实例,执行如下SQL语句即可。

INSERT INTO DISTRIBUE_ID(createtime) VALUES(NOW());
SELECT LAST_INSERT_ID();

这里需要注意以下几点:

● createtime字段的设计考量:

  • 该字段本身没有实际的业务含义
  • 主要目的是为了满足数据库表结构的完整性要求
  • 通过插入任意数据来触发数据库的自增ID机制
  • 这种设计虽然简单,但可能会造成数据冗余

● 使用独立MySQL实例生成分布式ID的局限性:

  1. 性能问题:
  • 每次获取ID都需要建立数据库连接
  • 高并发场景下会成为系统瓶颈
  • 网络延迟会影响ID获取速度
  • 无法满足毫秒级的ID生成需求
  1. 可靠性问题:
  • 存在单点故障风险
  • MySQL服务宕机会导致整个系统无法获取ID
  • 数据库维护期间无法提供服务
  • 网络分区时可能无法连接
  1. 扩展性问题:
  • 难以应对业务量快速增长
  • 垂直扩展有上限
  • 水平扩展实现复杂
  • 无法实现无缝扩容
  1. 其他问题:
  • 增加了系统复杂度
  • 需要维护额外的数据库连接池
  • 跨机房部署困难
  • ID生成效率受限于数据库性能

建议考虑更专业的分布式ID生成方案,如雪花算法、UUID等,这些方案在性能和可靠性方面都有更好的表现。

Redis生成ID

背景与需求

在分布式系统中,生成全局唯一ID是一个常见的需求。传统的数据库自增ID在并发量大的场景下可能面临性能瓶颈,因为:

  1. 数据库生成ID需要磁盘I/O操作
  2. 高并发时容易产生锁竞争
  3. 扩展性较差

Redis解决方案的优势

Redis作为内存数据库,具有以下特点使其适合生成ID:

  1. 单线程模型保证原子性操作
  2. 高性能(10万+ QPS)
  3. 支持持久化,保证数据安全

实现方式

1. 基础INCR命令
INCR id_counter
  • 每次执行自动将键值加1
  • 返回新的整数值作为ID
  • 示例:第一次调用返回1,第二次返回2
2. 批量生成ID(INCRBY)
INCRBY id_counter 1000
  • 一次性获取一段ID范围
  • 适合批量操作的场景
  • 服务端缓存这部分ID本地分配
3. 时间戳组合模式
INCR daily_counter
  • 生成格式:年月日(8位) + 自增序列(6位)
  • 例如:20230515-000001
  • 每天自动重置计数器

应用场景

  1. 订单系统:生成唯一订单号
  2. 日志系统:为每条日志标记唯一ID
  3. 分布式锁:基于ID实现锁机制
  4. 消息队列:消息的唯一标识

注意事项

  1. 需要设置适当的持久化策略(AOF或RDB)
  2. 集群环境下建议使用固定节点生成ID
  3. 可配合Lua脚本实现更复杂的ID生成逻辑
  4. 初始化时需要设置合适的初始值

性能对比

方案 QPS 优点 缺点
数据库自增ID 1k-5k 简单可靠 性能受限
Redis INCR 50k+ 高性能,原子性 需要维护Redis服务
UUID 100k+ 无需中心节点 ID较长,无序

通过合理使用Redis生成ID,可以在分布式系统中获得高性能的唯一ID生成方案。

也可以使用Redis集群来获取更高的吞吐量,假设一个集群中有5台Redis,可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5,那么:

A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25

网站公告

今日签到

点亮在社区的每一天
去签到