Skip to content

如何保证分库分表后 ID 的全局唯一性?

分库分表后,会产生一个新的问题:

原来数据库自增 ID 不再可靠了。

单库时代:

sql
id AUTO_INCREMENT

数据库天然保证:

text
1
2
3
4
5
...

唯一且递增。

但分库后:

text
db1:
1
2
3

db2:
1
2
3

db3:
1
2
3

不同库都会产生相同 ID。

这会导致:

  • 订单号冲突
  • 用户 ID 冲突
  • 数据无法合并
  • 路由失效

因此:

分布式系统必须解决「全局唯一 ID」问题。

一、理想的 ID 应该具备什么特征

一个优秀的分布式 ID 应满足:

1. 全局唯一

任何机器生成的 ID 都不能重复。

这是最基本要求。

2. 趋势递增

最好:

text
1001
1002
1003

而不是:

text
891723
17
9812374

递增 ID 对数据库更友好。

3. 高性能

高并发下:

每秒可能生成:

text
10万
100万
1000万

ID

不能成为系统瓶颈。

4. 高可用

发号器挂掉:

整个系统可能无法下单。

因此:

ID 服务必须高可用。

5. 长度适中

ID 太长:

  • 存储成本增加
  • 索引变大
  • 查询效率下降

二、最简单方案:UUID

很多人第一反应:

java
UUID.randomUUID()

例如:

text
550e8400-e29b-41d4-a716-446655440000

优点:

  • 绝对唯一
  • 本地生成
  • 无中心节点

但UUID 并不适合作为数据库主键。

原因:

1. 太长

128 位。

存储成本高。

2. 无序

完全随机。

例如:

text
1
1000000
3
500000

插入数据库时:

B+树频繁分裂。

3. 索引性能差

随机插入导致:

  • 页分裂
  • 页迁移
  • 缓存命中率下降

因此:

UUID 通常不适合作为数据库主键。

三、数据库自增主键

另一种方案:

专门建立一张表。

sql
CREATE TABLE sequence (
  id BIGINT AUTO_INCREMENT
)

每次获取 ID:

sql
INSERT INTO sequence VALUES(NULL)

然后:

sql
SELECT LAST_INSERT_ID()

优点:

  • 简单
  • 保证唯一
  • 天然递增

缺点:

发号器成为单点

所有请求都访问一个数据库。

例如:

text
10万QPS

全打到发号器。

数据库很快成为瓶颈。

因此:

无法支撑大型系统。

四、号段模式(Leaf 方案)

核心思想:

不要每次都找数据库要 ID。

而是一次申请一大段。

例如:

数据库记录:

text
当前最大ID = 10000

某个服务申请:

text
10001 ~ 11000

共 1000 个 ID。

之后:

本地直接发号:

text
10001
10002
10003
...
11000

无需访问数据库。

号段用完后:

再申请下一段。

例如:

text
11001 ~ 12000

五、号段模式为什么性能高

因为:

原来:

text
每生成1个ID
访问1次数据库

变成:

text
每生成1000个ID
访问1次数据库

数据库压力下降:

text
1000倍

因此:

系统吞吐量大幅提升。

六、号段模式的问题

虽然性能高,

但存在:

单点问题

数据库仍然存在。

只是压力变小。

号段浪费

例如:

申请:

text
10001~11000

结果服务重启。

只用了:

text
10001~10500

后面 500 个浪费。

ID 不要求连续。

因此浪费通常可以接受。

七、Snowflake(雪花算法)

这是互联网公司最常用方案。

Twitter 提出。

核心思想:

不依赖数据库。

完全本地生成。

64位结构:

text
1bit   符号位

41bit  时间戳

10bit  机器ID

12bit  序列号

生成结果:

text
时间
+
机器编号
+
自增序号

组合起来唯一。


八、Snowflake 为什么能保证唯一

例如:

机器1:

text
机器ID=1

生成:

text
时间戳 + 1 + 序列号

机器2:

text
机器ID=2

生成:

text
时间戳 + 2 + 序列号

机器编号不同。

自然不会冲突。

九、Snowflake 的优势

1. 无中心节点

无需访问数据库。

2. 性能极高

完全内存生成。

单机:

几十万到百万 QPS。

3. 趋势递增

前 41 位是时间戳。

因此:

整体递增。

4. 适合分布式

目前:

  • 美团 Leaf
  • 百度 UidGenerator
  • 滴滴 TinyID

本质都类似。


十、Snowflake 的问题

时钟回拨

例如:

当前时间:

text
1000

生成了一批 ID。

NTP 校时后:

系统时间变成:

text
900

那么:

可能生成重复 ID。

这是 Snowflake 最大风险。

常见解决方案:

  • 拒绝发号
  • 等待时间恢复
  • 备用机器

十一、实际工程选择

文章给出的思路是:

中小系统

可以:

  • UUID
  • 数据库自增

实现简单。

大型互联网系统

通常:

  • 号段模式
  • Snowflake

二选一。

如果追求:

简单可靠

选:

号段模式

美团 Leaf Segment。

如果追求:

极致性能

选:

Snowflake

本地生成。

十二、本章核心脉络

系统演进过程:

text
单库

数据库自增ID

分库分表

ID冲突

需要全局唯一ID

UUID

数据库发号器

号段模式

Snowflake

一句话总结

分库分表后,数据库自增 ID 无法保证全局唯一,因此需要独立的分布式发号机制。工程上最常见的两种方案是号段模式和 Snowflake 算法:前者通过批量申请 ID 降低数据库压力,后者通过时间戳、机器号和序列号组合实现本地高性能生成,两者本质上都是为了解决“全局唯一、高性能、高可用”的 ID 生成问题。

Released under the MIT License.