分布式ID设计 数据库主键自增

目录

[分布式 ID 设计举例](#分布式 ID 设计举例)

[1. 表结构的核心作用](#1. 表结构的核心作用)

[2. 如何生成唯一ID?](#2. 如何生成唯一ID?)

步骤1:插入一条新记录

步骤2:获取自动生成的ID

[3. 实际使用场景演示](#3. 实际使用场景演示)

[4. 为什么需要stub字段?](#4. 为什么需要stub字段?)

[5. 分布式系统中的应用](#5. 分布式系统中的应用)

[6. 常见问题解答](#6. 常见问题解答)

Q1:为什么不直接用AUTO_INCREMENT?

Q2:UUID_SHORT()是什么?

Q3:如果插入失败怎么办?

总结

[原理剖析 多服务器共享唯一索引](#原理剖析 多服务器共享唯一索引)

[1. 多服务器共享唯一索引的原理](#1. 多服务器共享唯一索引的原理)

[2. 示例演示:并发插入场景](#2. 示例演示:并发插入场景)

[时间点1:Server A插入记录](#时间点1:Server A插入记录)

[时间点2:Server B同时插入相同stub](#时间点2:Server B同时插入相同stub)

[时间点3:Server B重试并生成新stub](#时间点3:Server B重试并生成新stub)

[3. 为什么UUID_SHORT()几乎不会冲突?](#3. 为什么UUID_SHORT()几乎不会冲突?)

[4. 实际应用中的优化](#4. 实际应用中的优化)

总结

分库分表情况下怎么使用

方案1:专用ID生成库(推荐)

架构设计

优点

缺点

方案2:号段模式(性能优化)

改进思路

获取ID流程

方案3:UUID(完全去中心化)

方案特点

优点

缺点

方案4:雪花算法(Snowflake)

原理

实现方式

优点

缺点

方案对比与选择建议

分库分表下的最佳实践


分布式 ID 设计举例

我将通过一个更直观的分步示例来说明分布式ID设计 数据库主键自增工作原理。

让我们假设你是一家电商平台的开发者,需要为每个订单生成唯一的ID。

1. 表结构的核心作用

sequence_id表的核心是利用MySQL的自增主键特性来生成唯一ID。每次插入新记录时,数据库会自动分配一个比当前最大ID大1的新ID。

复制代码
CREATE TABLE `sequence_id` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, -- 自动生成的唯一ID
  `stub` char(10) NOT NULL DEFAULT '',              -- 占位符,确保每次插入唯一
  PRIMARY KEY (`id`),
  UNIQUE KEY `stub` (`stub`)                        -- 强制stub值唯一
) ENGINE=InnoDB;

2. 如何生成唯一ID?

步骤1:插入一条新记录

每次需要生成ID时,向表中插入一条新记录。stub字段需要填入一个唯一值(可以用UUID、时间戳等)。

复制代码
-- 插入新记录(UUID_SHORT()生成短唯一值)
INSERT INTO sequence_id (stub) VALUES (UUID_SHORT());
步骤2:获取自动生成的ID

插入成功后,使用LAST_INSERT_ID()函数获取刚生成的自增ID。

复制代码
-- 获取刚生成的ID
SELECT LAST_INSERT_ID();  -- 例如返回 1001

3. 实际使用场景演示

假设你需要为用户创建一个新订单,以下是完整流程:

复制代码
-- 步骤1:生成唯一ID
INSERT INTO sequence_id (stub) VALUES (UUID_SHORT());
SET @order_id = LAST_INSERT_ID();  -- 保存生成的ID到变量

-- 步骤2:使用这个ID创建订单
INSERT INTO orders (order_id, customer_id, product_name, amount)
VALUES (@order_id, 123, 'iPhone', 999.99);

-- 结果:orders表中会新增一条记录,order_id为刚生成的唯一值

4. 为什么需要 stub字段?

stub字段的唯一索引是这个方案的关键:

  • 防止重复插入 :如果两次插入的stub值相同,数据库会报错(唯一约束冲突),从而避免生成重复ID。
  • 保证原子性:数据库的唯一索引校验是原子操作,即使多个服务器同时插入,也只会有一个成功。

5. 分布式系统中的应用

如果你的系统有多个服务器(例如3个API服务器),每个服务器都可以独立执行上述插入操作,最终生成的ID仍然是全局唯一的:

复制代码
服务器1 → INSERT → 生成ID:1001
服务器2 → INSERT → 生成ID:1002
服务器3 → INSERT → 生成ID:1003

6. 常见问题解答

Q1:为什么不直接用 AUTO_INCREMENT
  • 如果有多个数据库实例(分布式系统),直接用AUTO_INCREMENT会导致ID冲突。而这个方案通过唯一索引确保全局唯一。
Q2: UUID_SHORT()是什么?
  • 它是MySQL内置函数,生成64位整数(类似1234567890123456789),几乎不会重复。你也可以用UUID()生成字符串形式的唯一值。
Q3:如果插入失败怎么办?
  • 如果stub重复导致插入失败,只需重试即可(概率极低)。

总结

这个表的设计本质是利用数据库的自增主键和唯一索引特性,在分布式环境中生成全局唯一ID。每次插入新记录就像"领号机"一样,保证每个号码都是唯一且递增的。

在分布式系统中,多个服务器共享同一个数据库时,唯一索引是共享的,这正是这个方案的核心优势。我通过具体例子解释这个机制:

原理剖析 多服务器共享唯一索引

1. 多服务器共享唯一索引的原理

假设你有3台API服务器(Server A、B、C),它们共同连接到同一个MySQL数据库:

复制代码
Server A ─┐
Server B ─┼──→ MySQL数据库(包含sequence_id表)
Server C ─┘

当3台服务器同时尝试插入记录时:

  1. 数据库会校验唯一索引 :如果两台服务器生成了相同的stub值,数据库会拒绝其中一个插入(报唯一约束冲突错误)。
  2. 成功插入的请求会获得唯一ID:由于自增主键的特性,每个成功插入的记录都会获得一个全局唯一的ID。

2. 示例演示:并发插入场景

时间点1:Server A插入记录
复制代码
-- Server A执行
INSERT INTO sequence_id (stub) VALUES ('abc123');  -- 假设stub值为'abc123'
  • 数据库检查stub唯一索引,发现'abc123'未被使用,插入成功。
  • 生成自增ID:1001
时间点2:Server B同时插入相同stub
复制代码
-- Server B执行
INSERT INTO sequence_id (stub) VALUES ('abc123');  -- 与Server A冲突的stub
  • 数据库检查stub唯一索引,发现'abc123'已存在,插入失败。
  • Server B收到错误:Duplicate entry 'abc123' for key 'stub'
时间点3:Server B重试并生成新stub
复制代码
-- Server B重新生成stub并插入
INSERT INTO sequence_id (stub) VALUES ('def456');  -- 新的唯一stub
  • 插入成功,生成自增ID:1002

3. 为什么UUID_SHORT()几乎不会冲突?

UUID_SHORT()生成的是64位整数,格式为:

复制代码
server_id * 2^56 + 当前时间戳(毫秒) + 自增计数器
  • server_id :数据库实例的唯一ID(由server_id参数决定)
  • 时间戳:保证不同时刻生成的值不同
  • 计数器:同一毫秒内生成的不同值

因此,只要:

  1. 各数据库实例的server_id不同
  2. 系统时钟同步

不同服务器生成相同UUID_SHORT()的概率极低(几乎为0)。

4. 实际应用中的优化

直接频繁插入数据库会影响性能,实际生产中通常会:

  1. 批量生成ID:一次获取100个ID缓存到本地,用完再获取。
  2. 使用数据库连接池:减少连接开销。
  3. 监控唯一索引冲突率:正常情况下冲突率应接近于0。

总结

  • 唯一索引是共享的:多个服务器连接同一个数据库时,唯一索引校验由数据库统一处理,确保全局唯一性。
  • UUID_SHORT()提供高唯一性:基于服务器ID和时间戳,几乎不会生成重复值。
  • 冲突处理:插入失败时重试即可,实际冲突概率极低。

这种方案巧妙利用了数据库的原子性特性,在分布式环境中实现了简单可靠的唯一ID生成。在分库分表的场景下,原方案需要调整,因为不同数据库实例的自增主键不再全局唯一。以下是几种适用于分库分表的唯一ID生成方案:

分库分表情况下怎么使用

方案1:专用ID生成库(推荐)

架构设计
  • 独立一个数据库(或集群)作为ID生成器,所有服务共享这个库。

  • 保留sequence_id表结构不变,所有服务器通过该表生成ID。

    应用服务1 ─┐
    应用服务2 ─┼──→ ID生成库(专用MySQL实例)
    应用服务3 ─┘

优点
  • 实现简单,复用原有方案。
  • 唯一性由数据库强保证。
缺点
  • ID生成可能成为性能瓶颈。
  • 存在单点故障风险(可通过主从复制解决)。

方案2:号段模式(性能优化)

改进思路
  • 应用每次从ID生成库批量获取一段ID(如1000个),缓存到本地使用,减少数据库访问。

  • 表结构增加current_max_idstep字段,记录当前分配的最大ID和步长。

    CREATE TABLE id_generator (
    biz_type varchar(50) NOT NULL COMMENT '业务类型',
    current_max_id bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '当前最大ID',
    step int(11) NOT NULL DEFAULT 1000 COMMENT '每次获取的号段长度',
    PRIMARY KEY (biz_type)
    ) ENGINE=InnoDB;

获取ID流程
  1. 初始化:插入业务类型记录。

    INSERT INTO id_generator (biz_type, current_max_id, step)
    VALUES ('order', 0, 1000);

  2. 获取号段

    -- 1. 开启事务
    START TRANSACTION;

    -- 2. 查询当前号段并更新
    SELECT current_max_id, step
    FROM id_generator
    WHERE biz_type = 'order'
    FOR UPDATE; -- 行锁

    -- 3. 更新当前最大ID(假设当前为1000,步长1000)
    UPDATE id_generator
    SET current_max_id = current_max_id + step
    WHERE biz_type = 'order';

    -- 4. 提交事务
    COMMIT;

    -- 返回结果:当前号段为 1001~2000

  3. 本地缓存使用 :应用本地缓存[1001, 2000],用完再获取下一段。

方案3:UUID(完全去中心化)

方案特点
  • 使用UUID()UUID_SHORT()直接生成全局唯一ID,不依赖数据库。

  • 例如:

    -- 生成标准UUID(字符串形式)
    SELECT UUID(); -- 输出:550e8400-e29b-41d4-a716-446655440000

    -- 生成短UUID(数字形式,需数据库配置server_id)
    SELECT UUID_SHORT(); -- 输出:1234567890123456789

优点
  • 完全去中心化,无性能瓶颈。
  • 实现简单,无需额外维护ID生成库。
缺点
  • ID是随机的,非递增,不适合作为数据库主键(性能较差)。
  • UUID字符串占用空间大(36字节),UUID_SHORT可能存在时钟回拨问题。

方案4:雪花算法(Snowflake)

原理
  • 将ID划分为:时间戳 + 工作机器ID + 序列号

  • 例如:

    0 | 0001100 10100010 10111110 10001001 | 00001 | 00000 0000000000
    └┘ └─────────────────────────────┘ └────┘ └──────────────────┘
    1bit 41bit时间戳 5bit机器ID 12bit序列号

实现方式
  • 独立服务:搭建专门的ID生成服务,基于Snowflake算法生成ID。
  • 客户端库:在应用中集成Snowflake算法库(需分配唯一机器ID)。
优点
  • 高性能(本地生成,无网络开销)。
  • 趋势递增(适合数据库索引)。
缺点
  • 需要分配和管理机器ID。
  • 依赖系统时钟(时钟回拨会导致ID重复)。

方案对比与选择建议

|-------------|-------------|--------------|----------------|
| 方案 | 优点 | 缺点 | 适用场景 |
| 专用ID生成库 | 实现简单,强一致性 | 性能瓶颈,单点风险 | 中小规模系统,ID生成不频繁 |
| 号段模式 | 高性能,减少数据库访问 | 架构复杂度增加 | 高并发场景 |
| UUID | 完全去中心化,无依赖 | ID非递增,占用空间大 | 对ID有序性无要求的场景 |
| 雪花算法 | 高性能,趋势递增 | 依赖时钟,需管理机器ID | 大规模分布式系统 |

分库分表下的最佳实践

  1. 优先考虑号段模式:在高性能和实现复杂度之间取得平衡。
  2. 结合数据库分片
    • 按业务类型分库(如订单库、用户库)。
    • 每个库维护独立的id_generator表。
  1. 监控与灾备
    • 监控ID生成服务的QPS和响应时间。
    • 对ID生成库做读写分离和主从备份。

通过合理设计,分库分表环境下依然可以实现可靠的全局唯一ID生成。

相关推荐
正在走向自律4 分钟前
X2Doris是SelectDB可视化数据迁移工具,安装与部署&使用手册,轻松进行大数据迁移
数据库·数据迁移·selectdb·x2doris·数据库迁移工具
KarrySmile6 分钟前
Day17--二叉树--654. 最大二叉树,617. 合并二叉树,700. 二叉搜索树中的搜索,98. 验证二叉搜索树
数据结构·算法·二叉树·二叉搜索树·合并二叉树·最大二叉树·验证二叉搜索树
凤年徐8 分钟前
【数据结构与算法】21.合并两个有序链表(LeetCode)
c语言·数据结构·c++·笔记·算法·链表
tuokuac8 分钟前
SQL中的LEFT JOIN
数据库·sql
tuokuac12 分钟前
SQL中的GROUP BY用法
数据库·sql
程序员老冯头16 分钟前
第三十二节 MATLAB函数
数据结构·算法·matlab
爱吃小土豆豆豆豆17 分钟前
登录校验一
java·大数据·数据库
lifallen21 分钟前
hadoop.yarn 带时间的LRU 延迟删除
java·大数据·数据结构·hadoop·分布式·算法
Albert Tan1 小时前
Oracle EBS 缺少adcfgclone.pl文件
数据库·oracle