【分布式利器:分布式ID】2、数据库自增ID实战:单库+多库分表方案 - 实践

# 第2篇:数据库自增ID实战:单库+多库分表方案(附SQL)

系列导读

上一篇我们搞懂了分布式ID的核心要求和单机自增ID的局限性。对于低中并发场景(如内部管理系统、日均订单1万以下),最简洁的方案是“数据库自增ID改造”——无需引入中间件,直接基于MySQL的自增特性实现分布式ID。

本文详解单库和多库分表的实现方案,附完整SQL,帮你快速落地。

一、适用场景

  • 低中并发(QPS≤1万);
  • 业务简单,不想引入Redis、中间件等复杂组件;
  • 需ID有序(支持分页查询、排序);
  • 代表业务:内部OA系统、小型电商订单、客户管理系统(CRM)。

二、方案1:单库自增ID(最快落地)

1. 核心原理

用一台“专门生成ID的数据库”,创建一个ID生成表,通过MySQL的AUTO_INCREMENT特性生成全局唯一ID。所有业务服务需要ID时,向这台数据库插入数据,获取自增ID。

2. 实战SQL(MySQL)

2.1 创建ID生成表
-- 全局ID生成表(按业务类型分表,避免不同业务ID冲突)
CREATE TABLE `id_generator` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '全局唯一ID',
`biz_type` varchar(32) NOT NULL COMMENT '业务类型(如order、user、pay)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_biz_type` (`biz_type`) COMMENT '同一业务类型只能有一条记录,用于获取下一个ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='全局ID生成表';
2.2 插入初始数据(按业务类型初始化)
-- 初始化订单、用户、支付3类业务的ID记录
INSERT INTO `id_generator` (`biz_type`, `create_time`) VALUES
('order', NOW()),
('user', NOW()),
('pay', NOW());
2.3 获取分布式ID(Java代码示例)

通过“更新+查询”获取自增ID,确保原子性:

@Service
public class IdGeneratorService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 获取指定业务的分布式ID
public Long getDistributedId(String bizType) {
// 1. 更新记录,自增ID(MySQL AUTO_INCREMENT自动生效)
String updateSql = "UPDATE id_generator SET create_time = NOW() WHERE biz_type = ?";
int affected = jdbcTemplate.update(updateSql, bizType);
if (affected == 0) {
throw new RuntimeException("业务类型" + bizType + "未初始化");
}
// 2. 查询自增后的ID
String selectSql = "SELECT id FROM id_generator WHERE biz_type = ?";
return jdbcTemplate.queryForObject(selectSql, Long.class, bizType);
}
}
2.4 调用示例
// 获取订单ID
Long orderId = idGeneratorService.getDistributedId("order");
System.out.println("生成订单ID:" + orderId); // 输出:1、2、3...(全局唯一)

3. 优点&缺点

  • 优点:实现最简单,无需中间件;ID有序,支持排序;开发成本低;
  • 缺点:单数据库瓶颈(QPS上限约1万);数据库宕机后无法生成ID(可用性低)。

三、方案2:多库分表自增ID(解决单库瓶颈)

1. 核心原理

部署多台ID生成数据库(如3台),每台数据库的自增ID设置“不同起始值+相同步长”,避免重复。例如:

  • 库1:起始值=1,步长=3 → 生成ID:1、4、7、10…;
  • 库2:起始值=2,步长=3 → 生成ID:2、5、8、11…;
  • 库3:起始值=3,步长=3 → 生成ID:3、6、9、12…;
  • 全局ID无重复,且有序递增。

2. 实战SQL(3台MySQL数据库配置)

2.1 库1配置(起始值1,步长3)
-- 创建ID生成表(库1)
CREATE TABLE `id_generator` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '全局唯一ID',
`biz_type` varchar(32) NOT NULL COMMENT '业务类型',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_biz_type` (`biz_type`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
-- 设置自增步长(需修改MySQL配置,或执行以下SQL)
SET GLOBAL auto_increment_increment = 3; -- 步长=3
SET GLOBAL auto_increment_offset = 1;   -- 起始值=1
2.2 库2配置(起始值2,步长3)
CREATE TABLE `id_generator` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '全局唯一ID',
`biz_type` varchar(32) NOT NULL COMMENT '业务类型',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_biz_type` (`biz_type`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
SET GLOBAL auto_increment_increment = 3;
SET GLOBAL auto_increment_offset = 2;
2.3 库3配置(起始值3,步长3)
CREATE TABLE `id_generator` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '全局唯一ID',
`biz_type` varchar(32) NOT NULL COMMENT '业务类型',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_biz_type` (`biz_type`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
SET GLOBAL auto_increment_increment = 3;
SET GLOBAL auto_increment_offset = 3;
2.4 负载均衡获取ID(Java代码)

通过轮询或随机选择ID生成库,避免单库压力:

@Service
public class MultiDbIdGeneratorService {
// 3台ID生成库的JdbcTemplate
@Autowired
private List<JdbcTemplate> jdbcTemplateList;
  private AtomicInteger index = new AtomicInteger(0);
  // 轮询选择ID生成库
  public Long getDistributedId(String bizType) {
  // 轮询索引(0→1→2→0...)
  int dbIndex = index.getAndIncrement() % jdbcTemplateList.size();
  JdbcTemplate jdbcTemplate = jdbcTemplateList.get(dbIndex);
  // 执行更新+查询(逻辑同单库方案)
  String updateSql = "UPDATE id_generator SET create_time = NOW() WHERE biz_type = ?";
  jdbcTemplate.update(updateSql, bizType);
  String selectSql = "SELECT id FROM id_generator WHERE biz_type = ?";
  return jdbcTemplate.queryForObject(selectSql, Long.class, bizType);
  }
  }

3. 优点&缺点

  • 优点:解决单库瓶颈(3台库QPS可达3万+);ID有序;扩展简单(新增库时设置起始值=库数量+1,步长=库数量);
  • 缺点:需协调数据库起始值和步长(扩容时复杂);依赖数据库高可用(需主从复制,避免单库宕机);主从同步延迟可能导致ID重复。

四、避坑指南:2个关键问题的解决方案

1. 主从同步延迟导致的ID重复

  • 问题:主库生成ID后,同步到从库前主库宕机,从库提升为主库后,会重新生成相同ID;
  • 解决方案:
    • 只从主库获取ID(不访问从库);
    • 数据库部署主从+哨兵,快速切换主库,减少同步延迟窗口;
    • 给ID加前缀(如库1ID前缀=100,库2=200),彻底避免重复。

2. 数据库扩容时的步长调整

  • 问题:新增第4台库,原步长=3,需调整所有库的步长=4,风险高;
  • 解决方案:
    • 提前规划库数量(如按10台库设计,步长=10,初始只部署3台,剩余库预留起始值);
    • 新增库时,只调整新增库的起始值(如第4台起始值=4,步长=4),原库不变(1、4、7… → 仍按步长3生成,与新库ID不冲突)。

实战Tips

  1. 单库方案适合日均订单≤1万的场景,多库方案适合日均订单≤10万的场景;
  2. 生产环境中,ID生成库需部署主从,确保高可用;
  3. 避免用ID生成库存储业务数据,只用于生成ID,减少数据库压力。

下一篇预告

数据库自增ID适合低中并发,但高并发场景(如秒杀每秒10万+ID)会出现数据库瓶颈。
下一篇我们拆解“号段模式”——数据库+本地缓存的优化方案,QPS可达10万+,是中高并发场景的首选!
你在项目中用过多库分表自增ID吗?评论区分享你的踩坑经历~

posted on 2025-12-27 10:08  ljbguanli  阅读(0)  评论(0)    收藏  举报