# 核心流程与多种分片策略
作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
代码参考:[https://github.com/YangYingmeng/learning_shardingJDBC)
# 一、Sharding-JDBC 核心流程
Sharding-JDBC(ShardingSphere-JDBC)本质上是一个:
工作在“业务代码 与 真实数据库之间”的客户端增强型 JDBC 引擎
业务侧只感知 逻辑表 + 逻辑 SQL,而底层复杂的:
- 分库
- 分表
- 路由
- SQL 改写
- 结果归并
全部由 Sharding-JDBC 在 客户端本地内存中自动完成,数据库只负责一件事:
执行已经被改写完成的“真实物理 SQL”
# 1. 标准执行流程
一条 SQL 在 Sharding-JDBC 中的完整生命周期如下:
SQL 解析
→ SQL 优化
→ SQL 路由
→ SQL 改写
→ SQL 执行(数据库)
→ 结果归并
→ 返回最终结果
2
3
4
5
6
7
这是一条完整的 “逻辑 SQL → 物理 SQL → 逻辑结果” 的转换链路。
| 阶段 | 执行者 | 是否访问数据库 | 核心目的 |
|---|---|---|---|
| SQL 解析 | ✅ Sharding-JDBC | ❌ | 识别表、条件、分片键、SQL 类型 |
| SQL 优化 | ✅ Sharding-JDBC | ❌ | 分片维度裁剪、单播路由、绑定约束 |
| SQL 路由 | ✅ Sharding-JDBC | ❌ | 计算命中哪个库、哪张分表 |
| SQL 改写 | ✅ Sharding-JDBC | ❌ | 逻辑表 → 物理表,补充分片信息 |
| ✅ SQL 执行 | ✅ 数据库(MySQL) | ✅ | 真正执行物理 SQL |
| 结果归并 | ✅ Sharding-JDBC | ❌ | 内存合并、排序、分页、聚合 |
# 2. 各阶段具体解析
# SQL 解析
作用是: 把一条 SQL 解析成 Sharding-JDBC 能识别的结构信息,例如:
- 是
SELECT还是UPDATE - 访问了哪些逻辑表
- WHERE 中有哪些条件
- 是否出现了分片键(如
user_id、order_id)
如果 不做解析,Sharding-JDBC 根本无法知道该往哪个库发 SQL,分库分表也就失去了意义。
# SQL 优化
作用是: 把一条 SQL 解析成 Sharding-JDBC 能识别的结构信息,例如:
- 是
SELECT还是UPDATE - 访问了哪些逻辑表
- WHERE 中有哪些条件
- 是否出现了分片键(如
user_id、order_id)
如果 不做解析,Sharding-JDBC 根本无法知道该往哪个库发 SQL,分库分表也就失去了意义。
# SQL 路由
这是分库分表中最核心的一步:
✅ 根据 分片规则 + 分片键值 → 算出物理库 & 物理表
例如:
user_id = 41
分库规则:ds$->{user_id % 2}
→ 路由到 ds1
2
3
如果 WHERE 中没有分片键:
❌ 只能退化为:全库路由(全库 + 全分表扫描)
# SQL 改写
作用是:
- 逻辑表 → 物理表
- 自动填充 Snowflake 主键
- 绑定表同步改写分表后缀
- 分页 SQL 物理改写(limit 下推)
例如:
select * from product_order where user_id = 41
会被改写为:
select * from product_order_1 where user_id = 41
# SQL 执行 (由数据库执行)
此阶段:
- 改写后的 真实物理 SQL
- 被发送到对应的:
ds0ds1
- 由 MySQL Server → InnoDB 真正执行
如果路由命中多个分片:
✅ 数据库会真实执行 多条 SQL
这正是你在控制台看到多条 Actual SQL 的原因。
# 结果归并
当多个库 / 多张表分别执行完 SQL 后:
- 多个
ResultSet - 会统一返回给 Sharding-JDBC
由它在 内存中完成:
- 排序归并(ORDER BY)
- 分页归并(LIMIT)
- 聚合归并(COUNT / SUM)
- 普通结果合并
最终再返回给你的 Mapper / Service。
# 二、多种分片策略
ShardingSphere 的分片算法大致可以分为四类:
- 精准分片(PreciseShardingAlgorithm):等值查询
=、IN - 范围分片(RangeShardingAlgorithm):
BETWEEN、>、< - 复合分片(ComplexKeysShardingAlgorithm):多个分片键组合路由
- Hint 分片(HintShardingAlgorithm):不解析 SQL,由业务“强制指定”路由
# 1. 精准分片
适用场景:
- SQL 条件是等值:
where user_id = ?、where id in (1, 2, 3) - 典型用法:
- 分库:按
user_id精准路由到某一个库 - 分表:按主键
id精准路由到某张分表
- 分库:按
实现接口: PreciseShardingAlgorithm<T>
同一个接口既可以用在分库,也可以用在分表:
- 配在
database-strategy上 → 参与“选库”- 配在
table-strategy上 → 参与“选表”
YAML 使用方式:
actual-data-nodes决定“真实库 + 表”的集合database-strategy.standard配精准分库算法table-strategy.standard配精准分表算法
standard.sharding-column是单分片键,PreciseShardingAlgorithm也是针对“单字段”的精准路由逻辑。
Demo
public class CustomDBPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
/**
* @param dataSourceNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分片库中所有分片表的集合 tablesNames
* @param preciseShardingValue 分片属性,包括
* logicTableName 为逻辑表,
* columnName 分片健(字段),
* value 为从 SQL 中解析出的分片健的值
* @return
*/
@Override
public String doSharding(Collection<String> dataSourceNames, PreciseShardingValue<Long> preciseShardingValue) {
for (String datasourceName : dataSourceNames) {
String value = preciseShardingValue.getValue() % dataSourceNames.size() + "";
//ds0、ds1
if (datasourceName.endsWith(value)) {
return datasourceName;
}
}
return null;
}
}
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
public class CustomTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
/**
* @param dataSourceNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分片库中所有分片表的集合 tablesNames
* @param preciseShardingValue 分片属性,包括
* logicTableName 为逻辑表,
* columnName 分片健(字段),
* value 为从 SQL 中解析出的分片健的值
* @return
*/
@Override
public String doSharding(Collection<String> dataSourceNames, PreciseShardingValue<Long> preciseShardingValue) {
for (String datasourceName : dataSourceNames) {
String value = preciseShardingValue.getValue() % dataSourceNames.size() + "";
//product_order_0
if (datasourceName.endsWith(value)) {
return datasourceName;
}
}
return null;
}
}
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
# 在业务代码使用时 只需要使用逻辑表名
sharding:
# 表的配置
tables:
product_order:
actual-data-nodes: ds$->{0..1}.product_order_$->{0..1} # 如果未配置真实的物理库 会默认选择所有的物理库
key-generator:
props: # 配置workId
worker:
id: 1
column: id
type: SNOWFLAKE
database-strategy: # 自定义精准分库策略
standard:
sharding-column: user_id
precise-algorithm-class-name: com.yym.sharding.strategy.CustomTablePreciseShardingAlgorithm
table-strategy: # 自定义精准分表策略
standard:
sharding-column: id
precise-algorithm-class-name: com.yym.sharding.strategy.CustomDBPreciseShardingAlgorithm
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2. 范围分片
适用场景:
- SQL 使用范围条件:
BETWEEN、>、<等,例如:where id between 100 and 200where create_time >= '2025-01-01' and create_time < '2025-02-01'
实现接口: RangeShardingAlgorithm<T>
核心特点:
- 入参是一个 值区间:
RangeShardingValue - 返回值是一个 集合:可能命中多个库 / 多张表
YAML 使用方式:
- 同样挂在
database-strategy.standard或table-strategy.standard下 - 使用
range-algorithm-class-name指定范围分片算法类
精准算法 + 范围算法可以配在同一个 standard 策略下:
precise-algorithm-class-name负责=/INrange-algorithm-class-name负责BETWEEN/>/<ShardingSphere 会根据 SQL 条件类型自动选择用哪个。
Demo
public class CustomDBRangeShardingAlgorithm implements RangeShardingAlgorithm<Long> {
/**
* @param dataSourceNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分片库中所有分片表的集合 tablesNames
* @param shardingValue 分片属性,包括
* logicTableName 为逻辑表,
* columnName 分片健(字段),
* value 为从 SQL 中解析出的分片健的值
* @return
*/
@Override
public Collection<String> doSharding(Collection<String> dataSourceNames, RangeShardingValue<Long> shardingValue) {
Set<String> result = new LinkedHashSet<>();
//between 开始值
Long lower = shardingValue.getValueRange().lowerEndpoint();
//between 结束值
Long upper = shardingValue.getValueRange().upperEndpoint();
for (long i = lower; i <= upper; i++) {
for (String datasource : dataSourceNames) {
String value = i % dataSourceNames.size() + "";
if (datasource.endsWith(value)) {
result.add(datasource);
}
}
}
return result;
}
}
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
public class CustomRangeShardingAlgorithm implements RangeShardingAlgorithm<Long> {
/**
* @param dataSourceNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分片库中所有分片表的集合 tablesNames
* @param shardingValue 分片属性,包括
* logicTableName 为逻辑表,
* columnName 分片健(字段),
* value 为从 SQL 中解析出的分片健的值
* @return
*/
@Override
public Collection<String> doSharding(Collection<String> dataSourceNames, RangeShardingValue<Long> shardingValue) {
Set<String> result = new LinkedHashSet<>();
//between 开始值
Long lower = shardingValue.getValueRange().lowerEndpoint();
//between 结束值
Long upper = shardingValue.getValueRange().upperEndpoint();
for (long i = lower; i <= upper; i++) {
for (String datasource : dataSourceNames) {
String value = i % dataSourceNames.size() + "";
if (datasource.endsWith(value)) {
result.add(datasource);
}
}
}
return result;
}
}
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
# 在业务代码使用时 只需要使用逻辑表名
sharding:
# 表的配置
tables:
product_order:
actual-data-nodes: ds$->{0..1}.product_order_$->{0..1} # 如果未配置真实的物理库 会默认选择所有的物理库
key-generator:
props: # 配置workId
worker:
id: 1
column: id
type: SNOWFLAKE
database-strategy: # 自定义范围分库策略
standard:
sharding-column: user_id
range-algorithm-class-name: com.yym.sharding.strategy.CustomRangeShardingAlgorithm
table-strategy: # 自定义范围分表策略
standard:
sharding-column: id
precise-algorithm-class-name: com.yym.sharding.strategy.CustomDBRangeShardingAlgorithm
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3. 复合分片
适用场景:
✅ 当“分片规则不是靠单一字段决定”,而是需要多个分片键一起参与计算,例如:
user_id + id共同决定分片tenant_id + create_time同时参与路由- 不同字段有不同的优先级 / 组合逻辑
实现接口: ComplexKeysShardingAlgorithm<T>
核心区别:
PreciseShardingAlgorithm:一次只处理一个分片键ComplexKeysShardingAlgorithm:可以拿到 多个分片键对应的值集合
demo 里做的事非常典型:
- 从
ComplexKeysShardingValue里取出:id对应的所有值集合user_id对应的所有值集合
- 双层循环做组合:
user_id % 2 + "_" + id % 2 - 在
dataSourceNames里找后缀匹配的目标节点
这就是标准的:
“多分片键 → 算出一个组合后缀 → 匹配实际库/表名”
YAML 使用方式(概念层面):
- 通常在 ShardingSphere 官方推荐里,多分片键应该用:
complex.sharding-columns=xxx,yyycomplex.algorithm-class-name=...
复合分片本质是“多字段组合路由”,不仅是“多字段都参与取模”,还可以在算法里实现复杂业务规则(例如某些 order_type 强制走某一库)。
Demo
public class CustomComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
/**
* @param dataSourceNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分片库中所有分片表的集合 tablesNames
* @param complexKeysShardingValue 分片属性,包括
* logicTableName 为逻辑表,
* columnName 分片健(字段),
* value 为从 SQL 中解析出的分片健的值
* @return
*/
@Override
public Collection<String> doSharding(Collection<String> dataSourceNames,
ComplexKeysShardingValue<Long> complexKeysShardingValue) {
// 得到每个分片健对应的值
Collection<Long> orderIdValues = this.getShardingValue(complexKeysShardingValue, "id");
Collection<Long> userIdValues = this.getShardingValue(complexKeysShardingValue, "user_id");
List<String> shardingSuffix = new ArrayList<>();
// 对两个分片健取模的方式 product_order_0_0、product_order_0_1、product_order_1_0、product_order_1_1
for (Long userId : userIdValues) {
for (Long orderId : orderIdValues) {
String suffix = userId % 2 + "_" + orderId % 2;
for (String databaseName : dataSourceNames) {
if (databaseName.endsWith(suffix)) {
shardingSuffix.add(databaseName);
}
}
}
}
return shardingSuffix;
}
/**
* shardingValue 分片属性,包括
* logicTableName 为逻辑表,
* columnNameAndShardingValuesMap 存储多个分片健 包括key-value
* key:分片key,id和user_id
* value:分片value,66和99
*
* @return shardingValues 集合
*/
private Collection<Long> getShardingValue(ComplexKeysShardingValue<Long> shardingValues, final String key) {
Collection<Long> valueSet = new ArrayList<>();
Map<String, Collection<Long>> columnNameAndShardingValuesMap = shardingValues.getColumnNameAndShardingValuesMap();
if (columnNameAndShardingValuesMap.containsKey(key)) {
valueSet.addAll(columnNameAndShardingValuesMap.get(key));
}
return valueSet;
}
}
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
# 在业务代码使用时 只需要使用逻辑表名
sharding:
# 表的配置
tables:
product_order:
actual-data-nodes: ds$->{0..1}.product_order_$->{0..1} # 如果未配置真实的物理库 会默认选择所有的物理库
key-generator:
props: # 配置workId
worker:
id: 1
column: id
type: SNOWFLAKE
database-strategy: # 自定义精准分库策略
standard:
sharding-column: user_id, id
precise-algorithm-class-name: com.yym.sharding.strategy.CustomComplexKeysShardingAlgorithm
table-strategy: # 自定义精准分表策略
standard:
sharding-column: user_id, id
precise-algorithm-class-name: com.yym.sharding.strategy.CustomComplexKeysShardingAlgorithm
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 4. Hint分片
适用场景:
当你不希望“路由规则完全依赖 SQL where 条件”,而是希望: 由业务代码在运行时强制指定分片值,常见场景:
- 某些 SQL 无法改写,但你知道它应该走哪个库 / 表
- 跨库 JOIN / 报表类 SQL,不方便写分片键
- 某些灰度 / 多租户场景,需要“临时切库”
实现接口: HintShardingAlgorithm<T>
与前面几种最大的区别:
不再从 SQL 中解析分片键
分片值来自业务代码里的:
HintManager hintManager = HintManager.getInstance(); hintManager.addDatabaseShardingValue("product_order", 1); hintManager.addTableShardingValue("product_order", 0);1
2
3
实现上:
dataSourceNames同样是目标库 / 表的候选集合hitShardingValue.getValues()中就是你在业务里塞进去的分片值- 算法逻辑与精准分片类似,只是“分片值来源变了”
YAML 使用方式:
- 写在
database-strategy.hint、table-strategy.hint下:- 配数据库级 Hint 算法 → 控制选库
- 配表级 Hint 算法 → 控制选表
Hint 分片优先级很高,一旦开启,就是“强制路由”:即使 SQL 里带有 user_id 等分片键,也会以 Hint 的值为准。
Demo
public class CustomDBHintShardingAlgorithm implements HintShardingAlgorithm<Long> {
/**
* @param dataSourceNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分片库中所有分片表的集合 tablesNames
* @param hitShardingValue 分片属性,包括
* logicTableName 为逻辑表,
* columnName 分片健(字段),hit策略此处为空 ""
* <p>
* value 【之前】都是 从 SQL 中解析出的分片健的值,用于取模判断
* HintShardingAlgorithm不再从SQL 解析中获取值,而是直接通过
* hintManager.addTableShardingValue("product_order", 1)参数进行指定
* @return
*/
@Override
public Collection<String> doSharding(Collection<String> dataSourceNames, HintShardingValue<Long> hitShardingValue) {
Collection<String> result = new ArrayList<>();
for (String datasourceName : dataSourceNames) {
for (Long shardingValue : hitShardingValue.getValues()) {
String value = shardingValue % dataSourceNames.size() + "";
if (datasourceName.endsWith(value)) {
result.add(datasourceName);
}
}
}
return result;
}
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
public class CustomTableHintShardingAlgorithm implements HintShardingAlgorithm<Long> {
/**
* @param dataSourceNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分片库中所有分片表的集合 tablesNames
* @param hitShardingValue 分片属性,包括
* logicTableName 为逻辑表,
* columnName 分片健(字段),hit策略此处为空 ""
* <p>
* value 【之前】都是 从 SQL 中解析出的分片健的值,用于取模判断
* HintShardingAlgorithm不再从SQL 解析中获取值,而是直接通过
* hintManager.addTableShardingValue("product_order", 1)参数进行指定
* @return
*/
@Override
public Collection<String> doSharding(Collection<String> dataSourceNames, HintShardingValue<Long> hitShardingValue) {
Collection<String> result = new ArrayList<>();
for (String datasourceName : dataSourceNames) {
for (Long shardingValue : hitShardingValue.getValues()) {
String value = shardingValue % dataSourceNames.size() + "";
if (datasourceName.endsWith(value)) {
result.add(datasourceName);
}
}
}
return result;
}
}
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
# 在业务代码使用时 只需要使用逻辑表名
sharding:
# 表的配置
tables:
product_order:
actual-data-nodes: ds$->{0..1}.product_order_$->{0..1} # 如果未配置真实的物理库 会默认选择所有的物理库
key-generator:
props: # 配置workId
worker:
id: 1
column: id
type: SNOWFLAKE
database-strategy: # 自定义精准分库策略
hint:
algorithm-class-name: com.yym.sharding.strategy.CustomDBHintShardingAlgorithm
table-strategy: # 自定义精准分表策略
hint:
algorithm-class-name: com.yym.sharding.strategy.CustomTableHintShardingAlgorithm
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19