# 核心流程与多种分片策略

作者: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 执行(数据库)
→ 结果归并
→ 返回最终结果
1
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_idorder_id

如果 不做解析,Sharding-JDBC 根本无法知道该往哪个库发 SQL,分库分表也就失去了意义。

# SQL 优化

作用是: 把一条 SQL 解析成 Sharding-JDBC 能识别的结构信息,例如:

  • SELECT 还是 UPDATE
  • 访问了哪些逻辑表
  • WHERE 中有哪些条件
  • 是否出现了分片键(如 user_idorder_id

如果 不做解析,Sharding-JDBC 根本无法知道该往哪个库发 SQL,分库分表也就失去了意义。

# SQL 路由

这是分库分表中最核心的一步:

✅ 根据 分片规则 + 分片键值 → 算出物理库 & 物理表

例如:

user_id = 41
分库规则:ds$->{user_id % 2}
→ 路由到 ds1
1
2
3

如果 WHERE 中没有分片键

❌ 只能退化为:全库路由(全库 + 全分表扫描)

# SQL 改写

作用是:

  • 逻辑表 → 物理表
  • 自动填充 Snowflake 主键
  • 绑定表同步改写分表后缀
  • 分页 SQL 物理改写(limit 下推)

例如:

select * from product_order where user_id = 41
1

会被改写为:

select * from product_order_1 where user_id = 41
1

# SQL 执行 (由数据库执行)

此阶段:

  • 改写后的 真实物理 SQL
  • 被发送到对应的:
    • ds0
    • ds1
  • 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;
    }
}
1
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;
    }
}
1
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
1
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 200
    • where create_time >= '2025-01-01' and create_time < '2025-02-01'

实现接口: RangeShardingAlgorithm<T>

核心特点:

  • 入参是一个 值区间RangeShardingValue
  • 返回值是一个 集合:可能命中多个库 / 多张表

YAML 使用方式:

  • 同样挂在 database-strategy.standardtable-strategy.standard
  • 使用 range-algorithm-class-name 指定范围分片算法类

精准算法 + 范围算法可以配在同一个 standard 策略下

  • precise-algorithm-class-name 负责 = / IN
  • range-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;
    }
}
1
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;
    }
}

1
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

1
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,yyy
    • complex.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;
    }

}
1
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

1
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.hinttable-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;
    }
1
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;
    }
}
1
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19