# 基础配置
作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
代码参考:[https://github.com/YangYingmeng/learning_shardingJDBC)
# 一、主键策略对比
在分库分表场景下,主键的生成策略将直接影响系统的性能、扩展性以及故障恢复能力。常见方案主要包括以下四类:
# 数据库自增 ID
通过为不同数据库设置不同的 自增起始值 + 步长,避免跨库主键冲突:
DB1:单数 → 从 1 开始,每次 +2
DB2:偶数 → 从 2 开始,每次 +2
2
对应 MySQL 参数:
auto_increment_offsetauto_increment_increment
优点:
- 实现简单
- 有序递增,利于索引性能
缺点:
- 严重依赖数据库,扩容成本高
- 主从切换可能导致 发号不一致
- 高并发下存在 单点性能瓶颈
- 分库数量变化时,步长策略维护复杂
适用于:小规模、非核心分布式系统
# UUID
直接由程序生成:
优点:
- 本地生成,无网络消耗
- 理论上全局唯一
- 性能极高
缺点:
- 无序,不具备趋势递增特性
- UUID 字符串过长,占用存储空间大
- 对数据库索引极不友好(B+Tree 频繁随机写)
- 不适合订单、流水号等业务主键
适用于:日志系统、非核心业务唯一标识
# Redis 发号器(INCR / INCRBY)
利用 Redis 的原子自增能力:
DB1:从 1 开始,每次 +2
DB2:从 2 开始,每次 +2
2
优点:
- 原子性强,线程安全
- 性能远高于 MySQL
- 可保证全局唯一
缺点:
- 依赖 Redis,存在网络开销
- 系统架构复杂度增加
- Redis 高可用、持久化都需要额外维护
适用于:中等规模分布式系统
# Snowflake 雪花算法
由 Twitter 开源的分布式 ID 生成算法,特点是:
优点:
- 本地生成,不依赖数据库和网络
- 高性能,高并发场景稳定
- ID 基于时间戳,基本保证趋势递增
- 数据迁移、分库分表不受影响
缺点:
- 依赖系统时钟(必须防止时间回拨)
- 分布式部署时必须保证 workerId 唯一
# 二、Snowflake 雪花算法原理简析
# Snowflake 是什么?
Snowflake 是 Twitter 使用 Scala 实现的一种 分布式唯一 ID 生成算法,核心特点:
- 生成的 ID 全局唯一
- 性能极高
- 基于时间戳,基本有序递增
Snowflake 生成的是一个 long 类型数字:
- 占 8 字节(64 bit)
- 有符号范围:
-2^63 ~ 2^63 - 1
由于数据库主键通常不使用负数,因此实际可用范围是:
0 ~ 2^63 - 1
≈ 9.22 × 10^18
2
其经典结构如下:
0 | 41bit 时间戳 | 10bit 机器ID | 12bit 序列号

# Snowflake 两大经典踩坑问题
坑一:workerId 重复
在分布式部署下:
- 如果 多个实例的 workerId 相同
- 且在同一毫秒内生成 ID
则会导致:
生成的 ID 完全相同,直接主键冲突
正确做法: 保证同一时刻在线的所有实例,workerId 全局唯一
坑二:系统时间回拨
如果服务器发生 时间回退(如 NTP 同步、虚拟机迁移):
当前时间 < 上一次生成 ID 的时间
则可能出现:
新生成的 ID 比旧 ID 小,导致重复
正确做法, 生产环境必须:
- 增加时间回拨保护
- 或直接拒绝生成 ID 并报警
# 三、绑定表
绑定表指:分片规则完全一致的主表与子表。
例如:
product_order(订单表)product_order_item(订单明细表)
二者如果都按照 order_id 分片,则:
这两张表互为 绑定表
绑定表的核心价值:
- 多表 JOIN 只发生在 同一个分片内
- 避免出现 跨库 JOIN
- 不会产生 笛卡尔积
- 查询性能大幅提升
实战原则:
只要是强关联表,必须设计成绑定表
# 四、广播表
广播表指:在每一个分片库中都完整存储的一张公共表。
特点:
- 表结构完全一致
- 表数据完全一致
- 每个库各维护一份副本
典型场景:
- 字典表(状态、类型、枚举)
- 配置表(系统配置、广告配置)
- 公共维表(国家、币种、行政区划)
优点:
- 避免跨库 JOIN
- 查询时始终走本库
- 逻辑更简单,性能更稳定
最佳实践:
使用分库分表中间件时,表名、字段名尽量避免使用 SQL 关键字,否则容易产生难以排查的 SQL 路由异常。
# 五、配置实战(分库分表 + 绑定表 + 广播表)
本示例基于如下结构:
创建两个物理库:
sharding_order_0sharding_order_1
物理表结构:
| 表类型 | 表名 |
|---|---|
| 主表 | product_order_0 / product_order_1 |
| 绑定表 | product_order_item_0 / product_order_item_1 |
| 广播表 | ad_config |
分片策略:
- 分库策略:按照
user_id % 2 - 分表策略:按照
product_order.id % 2 - 订单表与明细表为绑定表
- 广告配置表为广播表
- 主键采用 Snowflake 雪花算法
# 六、ShardingSphere 分库分表配置
server:
port: 8080
spring:
application:
name: test-shardingJDBC
main:
allow-bean-definition-overriding: true
shardingsphere:
props:
sql.show: true
datasource:
names: ds0, ds1
# 物理库 1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: "jdbc:mysql://115.159.195.151:13306/sharding_order_0"
username: root
password: MySql123++
# 物理库 2
ds1:
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: "jdbc:mysql://115.159.195.151:13306/sharding_order_1"
username: root
password: MySql123++
# 在业务代码使用时 只需要使用逻辑表名
sharding:
# 未指定分库策略, 则按照默认策略进行分库, ds0/1 用户id%2
default-database-strategy:
inline:
sharding-column: user_id
algorithm-expression: ds$->{user_id % 2}
# 表的配置
tables:
# 表名
product_order_item:
# 分表策略
table-strategy:
inline:
sharding-column: product_order_id
algorithm-expression: product_order_item_$->{product_order_id % 2}
# 分库分表策略本质上只是算名的函数, 此处才是声明物理数据库的范围,在该范围内使用对应的分库分表策略
actual-data-nodes: ds$->{0..1}.product_order_item_$->{0..1}
product_order:
table-strategy:
inline:
sharding-column: id # 分表策略 按照用户id取模
algorithm-expression: product_order_$->{id % 2}
actual-data-nodes: ds$->{0..1}.product_order_$->{0..1}
key-generator: # 自定义主键生成策略
column: id
type: SNOWFLAKE
props:
worker:
id: 1
ad_config:
key-generator:
type: SNOWFLAKE
column: id
# 广播表
broadcast-tables: ad_config
# 绑定表
binding-tables:
- product_order, product_order_item
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72