江河分流:分库分表之Sharding-JDBC

序言

  随着业务数据量不断增长,单表数据量过大容易带来索引膨胀、查询变慢、写入压力集中等问题。当数据规模达到一定程度后,分库分表就成为常见的数据库水平扩展方案。

  但真正开始接触 ShardingSphere 后,很多人会发现,分库分表本身并不难,难的是理解它背后的路由模型:什么是逻辑库、物理库、逻辑表和物理表?分片键应该如何选择?分片策略和分片算法到底有什么区别?为什么同样是一个 employee_id,既可以用于分库,又可以用于分表?Standard、Complex、Hint、Inline、None 这些策略又分别解决什么问题?

  更进一步,当 SQL 中出现 =、IN、BETWEEN、> 等不同条件时,ShardingSphere 又是如何识别分片条件,并最终计算出应该访问哪些库、哪些表的?

  本文不单纯从配置项出发,而是尝试沿着一条完整的「分片概念 → 分片策略 → 分片算法 → SQL 路由 → 版本演进 → 实战案例」路径,把 ShardingSphere 的分片机制串起来理解。文章还会结合 GPS 轨迹这类典型的大数据量业务场景,演示员工维度、时间维度以及复合维度下的不同分库分表方案,并进一步讨论实际设计中容易出现的分片空洞、数据分布不均以及库表如何合理组合等问题。

  希望通过这些案例,最终建立起一个整体认知,能够从“数据为什么要这样路由”的角度理解它的设计。

ShardingJDBC 简介

  ShardingJDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

  • 适用于任何基于 JDBC 的ORM框架,如:JPA, Hibernate, Mybatis, SpringJDBC Template 或直接使用 JDBC;
  • 支持任何第三方的数据库连接池,如:DBCP,C3PO,BoneCP,HikariCP 等;
  • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL, Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。

核心概念

  要理解分库分表,核心需要记住一些概念:逻辑库、物理库、逻辑表、物理表、分片策略。

逻辑库和物理库

逻辑库

  逻辑库是应用程序看到的数据库,是对多个物理数据库的抽象。

  在代码中,我们仍然可以按照普通数据库的方式进行开发。

  例如在 MyBatis-Plus 仍然可以执行SELECT * FROM t_gps_track;这个 SQL 去查询轨迹数据,应用并不需要知道gps_001、gps_002、gps_003这些具体数据库。

物理库

  物理库是真实存在的 MySQL 数据库。

  例如gps_001、gps_002两个数据库,它们可能分别运行在相同的 MySQL 实例上,也可能运行在不同的 MySQL 实例上,比如 MySQL-01 实例存放了gps_001库,而gps_002库位于MySQL-02实例上。

逻辑库 VS 物理库

  简而言之,逻辑库是对外统一的数据库概念,而物理库才是真正存储数据的 MySQL 数据库。

逻辑表和物理表

  数据库层面解决了之后,表同样需要进行拆分。

  如果未经过分库分表处理,原来我们只有一张单表:t_gps_track,在 Sharding-JDBC 分库分表后,比如说分为了t_gps_track_01、t_gps_track_02、t_gps_track_03三张表。

逻辑表

  那么,分库分表后,从 Java 应用角度看SELECT * FROM t_gps_track WHERE user_id = 1001;,应用程序永远操作的是t_gps_track这张表,虽然这张表实际上并不存在,但是此时它逻辑上代表了拆分过的多张表,此时t_gps_track就被称为逻辑表。

物理表

  t_gps_track_01、t_gps_track_02、t_gps_track_03这三张拆分的表,就叫做物理表。

逻辑表 VS 物理表

  逻辑表实际上并不存在,是应用程序看到的表,真正存储数据的表叫物理表。

分片策略

  到这里,我们知道了逻辑库、物理库、逻辑表、物理表这些概念,但是还有一个最重要的问题:你打算怎么把这些数据拆开?即到底应该对数据以什么作为依据,通过什么样的算法,分到哪个数据库、哪张表?

  这由分片策略决定,它主要回答几个问题:

  • 按哪个字段拆?
  • 用哪种算法去计算?
  • 拆成多少个库、多少张表?

  这几个问题,引出了新的三个概念,分片策略本质上是这三个概念的组合,下面我们来分别探讨下它们。

三个概念

分片键

  从需要分库分表的表结构中,可以选择一个或多个字段充当数据的拆分依据,选择的相关字段被称为分片键。

  举个例子,现在有一张 GPS 轨迹表,其字段包括:id、user_id、longitude、latitude、create_time这些字段,当我们:

  • 选择单一的字符字段,比如user_id作为拆分依据,那么user_id就是分片键
  • 选择单一的时间字段,比如create_time作为拆分依据,那么create_time就是分片键
  • 选择复合的多个字段,比如user_id + create_time作为拆分依据,那么user_id + create_time就是复合的分片键

分片算法

  当选择完分片键后,需要通过某种算法将数据分为多片,这就是分片算法,常见的分片算法如下表:

分片算法 说明 优点 缺点
哈希取模 用某个数字取余数,像“轮流分班” 数据分布均匀 扩容时要重新分,迁移成本高
一致性哈希 把所有分片放在一个环上,数据顺时针找最近的分片 扩容时只影响少量数据 实现复杂,可能出现数据倾斜
范围分片 按数值范围划分,例如 ID 1~100万去 1 库 查询范围方便 容易产生热点库
枚举/列表分片 按固定值映射,例如北京去 1 库,上海去 2 库 简单直观 只适合固定分类的字段
日期分片 按月/天拆分,例如订单表按月分表 时间范围查询方便 历史数据可能冷热不均
复合分片 多个字段一起决定位置,例如先按地区,再按用户 ID 哈希 更灵活 规则复杂

  实际项目中,很多时候不是只用一种,而是组合使用。

库级/表级分片策略

  分库分表,分库分表,其实存在三种组合:

  • 只分库
  • 只分表
  • 既分库又分表

  因此,我们在讨论分片策略时,有库级的分片策略和表级的分片策略之说,所以,在实际的业务过程中,会灵活的组合运用,需要注意这两个维度的差异。

小节

  分片策略,是分库分表的核心,简而言之就是一句话:我们需要根据业务「选择合适的分片键 + 选择合适的分片算法」决定「数据去往哪个库或者哪张表」。

ShardingSphere 分片策略

  ShardingSphere 提供了 standard、complex、hint、inline、none 5 种分片策略。

分片策略 核心定义 分片键 SQL 是否需要携带分片键 典型场景
standard 标准 单分片键,按照统一的标准规则进行路由 单个 需要 user_id、order_id、tenant_id 等单字段分片
complex 复合 多个分片键共同参与路由 多个 需要 user_id + create_time 等组合分片
inline 行表达式 将简单分片算法直接写成 Groovy 表达式 单个 需要 user_id % 2、order_id % 4 等简单规则
hint 强制 不从 SQL 中获取分片键,而由业务代码直接指定分片 没有固定 SQL 分片键 不需要 按登录用户、租户、地域、业务上下文路由
none 不分片 明确指定当前逻辑表不进行分片 无 — 某张表只存在一个库/表,或广播/普通表

  在实际使用中,ShardingSphere 的分片策略可以分别作用于分库和分表两个维度:

  • 分库策略(database-strategy):根据分片键和分片算法,决定数据应该路由到哪个物理库(数据源), 完整属性示例:
    • spring.shardingsphere.rules.sharding.tables.xxx.database-strategy
  • 分表策略(table-strategy):根据分片键和分片算法,决定数据应该路由到哪个物理表,完整属性示例:
    • spring.shardingsphere.rules.sharding.tables.xxx.table-strategy

  下面为一个 gps 轨迹表分别配置库分库策略和分表策略样例的 demo 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 作用的库和表
spring.shardingsphere.rules.sharding.tables.gps_track.actual-data-nodes=ds$->{0..1}.gps_track$->{20260726..20260806}
# 库级别分片配置:指定库级别分片策略、分片键、分片算法名称
spring.shardingsphere.rules.sharding.tables.gps_track.database-strategy.standard.sharding-column=employee_id
spring.shardingsphere.rules.sharding.tables.gps_track.database-strategy.standard.sharding-algorithm-name=algo-db-eid

# 表级别分片配置:指定表级别分片策略、分片键、分片算法名称
spring.shardingsphere.rules.sharding.tables.gps_track.table-strategy.standard.sharding-column=gps_time
spring.shardingsphere.rules.sharding.tables.gps_track.table-strategy.standard.sharding-algorithm-name=algo-day-interval

# 库级别分片使用的算法的具体配置
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-db-eid.type=INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-db-eid.props.algorithm-expression=ds$->{employee_id % 2}

# 表级别分片使用的算法的具体配置
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.type=INTERVAL
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-pattern=yyyy-MM-dd HH:mm:ss
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-lower=2026-07-26 00:00:00
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-upper=2026-08-07 00:00:00
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.sharding-suffix-pattern=yyyyMMdd
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-interval-unit=DAYS

五种分片策略

标准分片策略

  标准分片策略(standard)适用于具有单一分片键的标准分片场景。

  standard 策略支持精确分片,即在 SQL 中包含=、in 操作符,以及范围分片,包括 BETWEEN AND、>、<、>=、<= 等范围操作符。

  该策略下有两个属性,分片字段 shardingColumn 和分片算法名 shardingAlgorithmName。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
shardingsphere:
rules:
sharding:
tables:
t_order: # 逻辑表名称
# 数据节点:数据库.分片表
actual-data-nodes: db$->{0..1}.t_order_${1..10}
# 分库策略
databaseStrategy:
standard: # 用于单分片键的标准分片场景
shardingColumn: order_id # 分片列名称
shardingAlgorithmName: # 分片算法名称
tableStrategy: # 分表策略,同分库策略

行表达式分片策略

  行表达式分片策略(inline)适用于具有单一分片键的简单分片场景,支持 SQL 语句中 =、IN 操作符。

  inline 策略支持在配置属性 algorithm-expression 中书写 Groovy 表达式,用来定义对分片健的运算逻辑,无需单独定义分片算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
shardingsphere:
rules:
sharding:
tables:
t_order: # 逻辑表名称
# 数据节点:数据库.分片表
actual-data-nodes: db$->{0..1}.t_order_${1..10}
# 分库策略
databaseStrategy: # 分库策略
inline: # 行表达式类型分片策略
algorithm-expression: db$->{order_id % 2} # Groovy 表达式
tableStrategy: # 分表策略,同分库策略

复合分片策略

  复合分片策略(complex)适用于多个分片键的复杂分片场景,属性 shardingColumns 中多个分片健以逗号分隔。支持 SQL 语句中 >、>=、<=、<、=、IN 和 BETWEEN AND 等操作符。

  比如:我们希望通过 user_id 和 order_id 等多个字段共同运算得出数据路由到具体哪个分片中,就可以应用该策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
shardingsphere:
rules:
sharding:
tables:
t_order: # 逻辑表名称
# 数据节点:数据库.分片表
actual-data-nodes: db$->{0..1}.t_order_${1..10}
# 分库策略
databaseStrategy: # 分库策略
complex: # 用于多分片键的复合分片场景
shardingColumns: order_id,user_id # 分片列名称,多个列以逗号分隔
shardingAlgorithmName: # 分片算法名称
tableStrategy: # 分表策略,同分库策略

Hint 分片策略(了解)

  Hint 强制分片策略相比于其他几种分片策略稍有不同,该策略无需配置分片健,由外部指定分库和分表的信息,可以让 SQL 在指定的分库、分表中执行。

  使用场景:

  • 强制在指定数据库进行某些数据操作
  • 分片字段不存在 SQL 和数据库表结构中,而存在于外部业务逻辑

  比如,我们希望用 user_id 做分片健进行路由订单数据,但是 t_order 表中也没 user_id 这个字段啊,这时可以通过 Hint API 手动指定分片库、表等信息,强制让数据插入指定的位置。

不分片策略

  不分片策略,字如其意,如果设置了该策略,那么对逻辑表的所有操作将会执行全库表路由。具体配置属性如下:

  • spring.shardingsphere.rules.sharding.tables.xxx.database-strategy:none
  • spring.shardingsphere.rules.sharding.tables.xxx.table-strategy:none

不同分片策略下的路由流程

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
                       SQL
│
▼
┌───────────────┐
│ 路由入口 │
└───────┬───────┘
│
┌─────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
Standard Complex Hint
│ │ │
提取一个键 提取多个键 从程序获取
│ │ │
▼ ▼ ▼
StandardStrategy ComplexStrategy HintStrategy
│ │ │
▼ ▼ ▼
标准算法 复合算法 Hint算法
│ │ │
└─────────────────┼──────────────────┘
│
▼
路由结果
│
┌────────┴────────┐
▼ ▼
数据库 数据表


Inline None
│ │
▼ ▼
表达式计算 不分片
│ │
▼ ▼
路由结果 原表

ShardingSphere 分片算法

  对 ShardingSphere 而言,部分常用的分片策略无法单独使用,需要搭配不同的分片算法才能生效。

  在 ShardingSphere 中,提供了多种内置分片算法,可以根据业务的分片规则选择合适的算法。常见算法如下:

算法 适合场景 特点 生产建议
MOD(了解) user_id、order_id 这类数值型分片键 直接取模运算,分布较均匀 ⭐⭐
INLINE(了解) 简单规则,例如 user_id % 4 通过表达式直接计算分片位置 ⭐⭐⭐
HASH_MOD 用户、订单、设备等 ID 型数据 先 Hash 再取模,分布通常更均匀 ⭐⭐⭐⭐⭐
VOLUME_RANGE 希望每个分片控制在大致固定数据量 根据数据量划分范围 ⭐⭐
BOUNDARY_RANGE 按 ID、金额、时间等划固定区间 0~100万 → 表1,100万~200万 → 表2 ⭐⭐⭐
INTERVAL 按固定时间周期分片 如每月一张表、每天一张表 ⭐⭐⭐⭐
AUTO_INTERVAL 按时间自动划分,例如日志、流水 时间范围分片,配置相对灵活 ⭐⭐⭐⭐
HINT_INLINE SQL 中没有合适的分片键,但业务代码知道路由信息 通过 Hint 强制指定分片 ⭐⭐
COMPLEX_INLINE 多个分片键共同参与路由 例如 tenant_id + user_id ⭐⭐
CLASS_BASED 分片规则比较复杂,内置算法无法满足 自己写 Java 分片算法 ⭐⭐⭐⭐

MOD(了解)

  MOD 比较适合连续自增或均匀增长的分片键。

  MOD 对分片键进行取模的计算公式为:分片 = sharding_value % 分片数量

  例如:order_id % 4,最终将数据分散到:t_order_0、t_order_1、t_order_2、t_order_3,适合 ID 分布比较均匀的场景。

INLINE(了解)

  适合简单、固定的取模分片规则。

  具体规则是通过 Groovy 表达式直接计算目标库表。例如:algorithm-expression: t_order_${order_id % 4}表示:order_id = 1001 → 1001 % 4 = 1 → t_order_1

HASH_MOD(常用)

  HASH_MOD 会先对分片键进行哈希,再取模,其计算公式为hash(sharding_value) % 分片数量

  例如:user_id → Hash % 8 → 0 ~ 7

VOLUME_RANGE

略。

BOUNDARY_RANGE

  BOUNDARY_RANGE 分片算法适合按照连续数值范围进行分片的场景。

  具体规则为按照预先定义的范围边界进行路由。

  例如:

  • 0 ~ 999999 → t_order_0
  • 1000000 ~ 1999999 → t_order_1
  • 2000000 ~ 2999999 → t_order_2

INTERVAL(常用) / AUTO_INTERVAL

  INTERVAL / AUTO_INTERVAL主要用于时间分片。

  非常适合订单、日志、GPS轨迹、操作记录、监控数据这类随着时间不断增长的数据。
  例如按照月份:

  • 2026-01 → t_order_202601
  • 2026-02 → t_order_202602
  • 2026-03 → t_order_202603

  INTERVAL 可以按照指定的时间范围和时间间隔进行分片;AUTO_INTERVAL 则根据时间范围、间隔等配置自动计算分片。

HINT_INLINE(了解)

  HINT 比较特殊,它不是从 SQL 中解析分片键,而是由业务代码主动告诉 ShardingSphere:当前请求 → tenant_001,然后根据这个 Hint 进行路由。

  适用于:

  • SQL 中没有分片键
  • 根据登录用户路由
  • 根据租户路由
  • 根据地域路由
  • 根据业务上下文路由

COMPLEX_INLINE

  略

CLASS_BASED

  CLASS_BASED 可以支持使用自己编写的 Java 类去实现分片算法。

  例如业务规则比较复杂:tenant_id → city_id → create_time → 自定义算法 → 计算目标库表

  当内置算法无法满足业务需求时,可以使用这种方式。

疑问

  ShardingSphere 分片策略和分片算法初看很难理解,比如我们会有以下疑问:

  • 它们有什么区别呢?
  • 为什么要分这么多的分片策略?
  • 不同分片策略如何支持的精确查询、范围查询?
  • 策略、匹配条件、算法逻辑上是怎么关联的?
  • 什么时候需要使用自定义分片算法?怎么定义?

  下面我们分别

策略与算法的区别

  从定义而言:分片策略决定“怎么接收分片条件”,分片算法决定“拿到分片值之后怎么算出目标库/表”。

  可以抽象成:

1
2
3
4
5
6
7
8
9
10
11
SQL / Hint
↓
分片策略 Strategy
↓
提取分片键、判断操作类型
↓
分片算法 Algorithm
↓
计算应该去哪些库 / 表
↓
最终路由结果

  例如SELECT * FROM t_order WHERE order_id = 1001;这条 SQL,假设分片键为order_id,分片策略为 Standard,若 order_id = 1001,则可以调用具体的分片算法,比如1001 % 4 = 1,路由到t_order_1查询。

这里:

  • Standard —— 策略
  • order_id —— 分片键
  • 1001 % 4 —— 算法
  • t_order_1 —— 最终路由结果

为什么要对单键多键制定两种策略?

  ShardingSphere 常见的策略包括:

策略 核心解决什么问题
Standard 单分片键,按 SQL 中的分片条件路由
Complex 多个分片键共同参与路由
Inline 单分片键 + 简单表达式直接计算
Hint 不依赖 SQL 中的分片键,由 Hint 指定路由依据
None 不进行分片

  那么,为什么要区分单键、多键、甚至无键?

  这本质上是因为:单键和多键的“路由输入”不同,ShardingSphere 需要用不同的接口模型把分片键传给算法。

  官方文档也明确把两者区分为:

  • StandardShardingStrategy:单个分片键
  • ComplexShardingStrategy:多个分片键

  可以从“算法到底需要什么输入”来理解。

单键:一个值就可以决定去哪

  比如下表按照 user_id 分表(规则user_id % 4):

1
2
3
4
5
CREATE TABLE order (
id BIGINT,
user_id BIGINT,
...
);

  如果执行 SQLSELECT * FROM order WHERE user_id = 1001;,ShardingSphere 只需要拿到分片键user_id和分片值1001,算法计算1001 % 4 = 1得到结果,于是路由到表order_1。

  所以单键策略非常简单:执行 SQL → 找到user_id→ 拿到 1001 → 分片算法计算 → 路由到order_1

  这就是 StandardShardingStrategy 适合的场景,它就是针对单分片键设计的。

多键:算法可能需要多个值共同决定

  在人员轨迹表中假设按照employee_id + create_time进行分片。

  例如:employee_id → 决定分库,create_time → 决定分表,SQL 为:

1
2
3
4
5
SELECT *
FROM gps_track
WHERE employee_id = 1001
AND create_time >= '2026-09-01'
AND create_time < '2026-10-01';

  这时候路由需要考虑:

1
2
employee_id = 1001
create_time = 2026-09-01 ~ 2026-10-01

  此时,两个分片键承担不同作用:

  • employee_id:决定数据库
  • create_time:决定具体月份/日期表

  所以算法需要同时获得:

1
2
3
4
{
employee_id: 1001,
create_time: 2026-09-01 ~ 2026-10-01
}

  然后由复杂分片算法自己决定:

1
2
3
4
5
6
7
ds_1
├── gps_track_20260901
├── gps_track_20260902
└── ...

ds_2
├── ...

  这就是 ComplexShardingStrategy 的意义:允许算法同时接收多个分片键以及它们对应的条件。 官方文档也说明复杂策略将多个分片键及其操作组合交给算法处理。

为什么不统一成“一个策略”?

  其实可以把它设计成一个统一接口,但会带来两个问题。

第一:单键场景会变复杂

  绝大多数简单分库分表其实都是:

  • user_id % 4
  • order_id % 8
  • tenant_id % 16

  如果全部设计成Map<分片键, 分片值>当然也能实现,但是对于user_id: 1001这种简单场景,接口就显得比较重。

  所以 ShardingSphere 把最常见的情况单独抽象出来:Standard 针对一个分片键简单路由。

第二:多键的组合关系无法统一规定

  假设有两个分片键tenant_id、user_id到底逻辑上应该怎么计算?

  可能是tenant_id % 4,也可能是user_id % 8,甚至(tenant_id + user_id) % 16,甚至tenant_id + user_id + create_time,或者:

  • tenant_id → 分库
  • user_id → 分表

  多种可能共同决定路由时,这些业务规则没有一个通用答案。

  因此 ShardingSphere 不替你规定多个分片键应该怎么组合,而是把这些信息交给 ComplexShardingAlgorithm,由你自己实现。

精确查询还是查询匹配?

  在分片策略中,不能简单地说:Standard 支持范围查询,Complex 不支持范围查询,Inline 不支持范围查询。

  更准确的是:不同策略能够接收不同类型的分片条件,而具体是否能够根据单值或范围条件进行“路由”,还取决于对应的分片算法是怎样实现的。

  为了支持单值或范围条件,分片算法可能是用接口继承的方式实现,也可能是用参数传递的方式实现,这在不同版本中实现上存在差异。

  例如WHERE order_id = 1001是精准条件=,而:WHERE order_id BETWEEN 1000 AND 2000是范围条件BETWEEN,二者对分片算法的要求不同。

Standard 策略下的匹配

  Standard 的核心特点:单分片键 + 区分精准查询和范围查询。

  例如分片键为order_id

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
                    SQL
│
↓
提取分片条件
│
↓
StandardShardingStrategy
│
判断条件类型
│
┌──────────┴──────────┐
↓ ↓
精确条件 范围条件
= / IN 等 BETWEEN / > / <
│ │
↓ ↓
PreciseShardingValue RangeShardingValue
│ │
└──────────┬──────────┘
↓
StandardShardingAlgorithm
│
执行具体算法
│
┌──────────┴──────────┐
↓ ↓
t_order_1 t_order_0、t_order_1...
│
↓
最终路由

① 精准查询

  如果查询 SQL 为WHERE order_id = 1001,Standard 策略发现分片键order_id,操作为=,那么会交给精准分片算法(根据哈希、范围分片算法存在多种实现),最终路由到t_order_1

② 范围查询

  如果查询 SQL 为WHERE order_id BETWEEN 1000 AND 2000,这时候不能简单计算1000 % 4,因为逻辑上需要判断下面的这些值分别可能落在哪些分片:

1
2
3
4
5
1000
1001
1002
...
2000

  因此 Standard 可以配合范围分片算法(根据哈希、范围分片算法存在多种实现)处理范围条件。

精准、范围查询的版本差异

  在 ShardingSphere 不同版本中,对于精准、范围查询的架构上处理不太一样。

版本 算法组织方式 Standard 典型配置
3.x Precise / Range 等独立接口 直接配置算法类
4.x 仍以独立算法接口为主 preciseAlgorithmClassName + rangeAlgorithmClassName
5.x 统一 ShardingAlgorithm + 算法类型/名称 shardingAlgorithmName + type

  3.x/4.x 官方文档明确把 PreciseShardingAlgorithm、RangeShardingAlgorithm、ComplexKeysShardingAlgorithm 分开定义。

  5.x 则将内置算法按自动分片、标准、复合、Hint、Class Based 等类别组织,并通过算法类型配置使用。

4.x 的设计:Strategy 直接绑定算法

  以 ShardingSphere 4.1 为例:

1
2
3
4
5
StandardShardingStrategyConfiguration(
String shardingColumn,
PreciseShardingAlgorithm<?> preciseShardingAlgorithm,
RangeShardingAlgorithm<?> rangeShardingAlgorithm
)

  也就是说,Standard Strategy 本身就知道:精确算法是谁、范围算法是谁,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                ShardingStrategy
│
StandardShardingStrategy
│
┌─────────┴─────────┐
↓ ↓
PreciseShardingAlgorithm RangeShardingAlgorithm
(接口) (接口)
↑ ↑
│ │
┌─────────┼────────┐ ┌──────┼──────┐
↓ ↓ ↓ ↓ ↓ ↓
精确取模 精确哈希 自定义 ID范围 时间范围 自定义
算法实现 算法实现 算法实现 算法实现 算法实现 算法实现

  配置也比较直接:

1
2
3
4
standard:
sharding-column: user_id
precise-algorithm-class-name: com.xxx.UserPreciseAlgorithm
range-algorithm-class-name: com.xxx.UserRangeAlgorithm

  官方 4.1 配置就是这种模式:precise-algorithm-class-name 对应 PreciseShardingAlgorithm,range-algorithm-class-name 对应 RangeShardingAlgorithm。

4.x 的问题:算法和 Strategy 耦合比较明显

  假设:

1
2
3
4
Standard Strategy
│
├── PreciseAlgorithm A
└── RangeAlgorithm B

  如果以后又增加一种算法:HashAlgorithm、IntervalAlgorithm……
,Strategy 层就需要越来越多地认识各种算法接口,所以整体结构容易变成:

1
2
3
4
5
6
7
Strategy
│
├── PreciseShardingAlgorithm
├── RangeShardingAlgorithm
├── ComplexKeysShardingAlgorithm
├── HintShardingAlgorithm
└── ...

  这会让:“分片策略怎么组织路由”和:“具体算法怎么计算分片”之间产生比较强的耦合。

5.x 的核心变化:统一成 ShardingAlgorithm

  到了 ShardingSphere 5.x,核心抽象变成org.apache.shardingsphere.sharding.spi.ShardingAlgorithm,于是结构变成:

1
2
3
4
5
6
7
           ShardingAlgorithm
│
┌───────────────┼───────────────┐
↓ ↓ ↓
INLINE MOD INTERVAL
│ │ │
表达式 取模 时间

  这时候 Standard Strategy 不再需要:“我要找一个 PreciseShardingAlgorithm”、“我要找一个 RangeShardingAlgorithm”,而是:“我有一个 shardingAlgorithmName,去找到这个算法。”

  例如:

1
2
3
standard:
sharding-column: worker_id
sharding-algorithm-name: worker-db-inline

  然后:

1
2
3
4
5
sharding-algorithms:
worker-db-inline:
type: INLINE
props:
algorithm-expression: ds_${worker_id % 4}

  先给结论:3.x/4.x 更像是“策略直接持有具体算法对象/算法类”;5.x 更像是“策略只引用一个算法名称 → 算法注册中心根据 type 找到实现 → 初始化算法 → Strategy 调用统一的 ShardingAlgorithm 接口”。

4.x 到 5.x 配置的重大变化

  以前重点是:配置 → Java Class:

1
2
3
4
standard:
sharding-column: worker_id
precise-algorithm-class-name: com.xxx.MyPreciseAlgorithm
range-algorithm-class-name: com.xxx.MyRangeAlgorithm

  现在先配置策略:

1
2
3
standard:
sharding-column: worker_id
sharding-algorithm-name: worker-db-inline

  然后配置算法:

1
2
3
4
5
sharding-algorithms:
worker-db-inline:
type: INLINE
props:
algorithm-expression: ds_${worker_id % 4}

  重点变成:Strategy → 算法名称 → 算法配置 → type → SPI → 具体算法实现

  这就是 5.x 很重要的架构变化。

不同版本对 Precise 和 Range的影响

  以前:

1
2
3
4
5
Standard Strategy
│
├── PreciseShardingAlgorithm
│
└── RangeShardingAlgorithm

  现在层级抽离为传递参数,不同算法分别处理这两类参数:

1
2
3
4
5
6
7
8
9
10
11
12
Standard Strategy
│
↓
ShardingAlgorithm
│
├── 精确分片值
│ ↓
│ PreciseShardingValue
│
└── 范围分片值
↓
RangeShardingValue

  也就是说:Precise / Range 不再简单理解成“两种独立的算法类型接口”存在多种实现,而更多体现为 Standard Algorithm 接收到的两种不同分片值。

  ShardingSphere 5.x 将“分片策略”和“分片算法”进一步解耦:Strategy 负责组织分片键和路由流程,ShardingAlgorithm 负责具体分片计算,算法通过 type + props 配置并由 SPI 机制加载;Standard 根据 SQL 条件形成 Precise/Range 分片值,再交给所配置的 Algorithm 处理。

策略、匹配条件、算法三个维度差异

  当分片策略采用 Standard 策略时,Standard 用来确定单键策略,之后 Standard 根据 SQL 匹配条件构造为 Precise/Range,最后交给所配置的 Algorithm 处理路由。

  所以说 Standard 策略、Precise/Range 分片值、Algorithm,她们是三个维度的东西,Strategy 管“规则怎么组织”,Precise/Range 管“这次 SQL 给了什么类型的条件”,Algorithm 管“拿这个条件怎么算目标分片”。

  下面,我们用一个最简单的例子加深理解。

案例

  假设你的 GPS 表gps_track,数据库按照worker_id分片,配置:

1
2
3
standard:
sharding-column: worker_id
sharding-algorithm-name: worker-db-inline

  算法:

1
2
3
4
worker-db-inline:
type: INLINE
props:
algorithm-expression: ds_${worker_id % 4}

  然后执行 SQL SELECT * FROM gps_track WHERE worker_id = 10001;

  整个过程逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
① Strategy
Standard
↓
“我的分片键是 worker_id”

② SQL条件
worker_id = 10001
↓
“这是一个精确值”
↓
PreciseShardingValue

③ Algorithm(接收 PreciseShardingValue 参数)
INLINE
↓
10001 % 4
↓
ds_1

  所以:

  • Standard → 决定“怎么组织分片”
  • Precise → 描述“这次条件是什么样”
  • INLINE → 决定“具体怎么算”

第一维:Standard Strategy —— “怎么组织分片”

  首先看Standard Strategy,它属于分片策略 Strategy,它主要解决的问题是:“我要根据哪个分片键进行标准分片,以及如何把 SQL 中的分片条件交给算法。”

  例如:

1
2
3
standard:
sharding-column: worker_id
sharding-algorithm-name: worker-db-inline

  这里 Standard 告诉 ShardingSphere:我的分片键为worker_id、我的算法为worker-db-inline,所以 Standard 更像一个路由组织者 / 调度者。它自己不是10001 % 4,也不是10000 ~ 20000,它只是负责把这些信息组织起来。

第二维:Precise / Range —— “这次 SQL 给了什么条件”

  这是另外一个层次。

  例如WHERE worker_id = 10001的 SQL 给出了一个明确的值,所以形成PreciseShardingValue,可以理解成:

1
2
3
4
5
6
7
worker_id = 10001

↓

PreciseShardingValue {
worker_id = 10001
}

  而WHERE worker_id BETWEEN 10000 AND 20000SQL 给出的不是一个具体值,而是一个范围,所以形成RangeShardingValue,可以理解成:

1
worker_id ∈ [10000, 20000]

  所以Precise / Range回答的是:“这一次 SQL 给我的分片条件是什么类型?”而不是“应该使用什么算法?”

第三维:Algorithm —— “具体怎么算”

  现在已经知道worker_id = 10001是一个 Precise 条件,接下来还需要回答:10001 到底应该去哪一个数据库?这就是 Algorithm 的工作。

  例如:

1
2
type: INLINE
algorithm-expression: ds_${worker_id % 4}

  于是:

1
2
3
4
5
6
7
10001
↓
10001 % 4
↓
1
↓
ds_1

  所以 Algorithm 回答的是“给我分片条件,我如何计算目标数据库/表?”

算法中的代码处理

  以取模分片算法ModShardingAlgorithm为例子,其存在两个方法分别处理精确值和范围区间:

1
2
3
4
5
6
7
8
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Comparable<?>> shardingValue) {
String shardingResultSuffix = this.getShardingResultSuffix(this.cutShardingValue(shardingValue.getValue()).mod(new BigInteger(String.valueOf(this.shardingCount))).toString());
return (String)ShardingAutoTableAlgorithmUtil.findMatchedTargetName(availableTargetNames, shardingResultSuffix, shardingValue.getDataNodeInfo()).orElse((Object)null);
}

public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Comparable<?>> shardingValue) {
return this.containsAllTargets(shardingValue) ? availableTargetNames : this.getAvailableTargetNames(availableTargetNames, shardingValue);
}

小节

概念 所属 回答的问题
Standard Strategy 怎么组织分片和路由
Precise / Range 分片条件类型 这次 SQL 给的是单值还是范围
Algorithm ShardingAlgorithm 根据分片条件怎么算目标分片

  可以把它们看成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
          SQL
│
↓
┌──────────────────┐
│ Standard Strategy│
│ “怎么组织?” │
└────────┬─────────┘
↓
SQL中的分片条件
│
┌──────┴──────┐
↓ ↓
Precise Range
“单值” “范围”
│ │
└──────┬──────┘
↓
Algorithm
“怎么算?”
│
↓
目标分片

  某种程度上,它们并不是完全“独立”平行的“三个维度”,而是一个调用链上的三个层次:选择 Strategy → 匹配分片条件 → 传递给 Algorithm 计算确定路由

1
2
3
4
5
第一步:Strategy
↓
第二步:确定/组织分片条件
↓
第三步:Algorithm计算

  所以“不同维度”更准确地说是:它们解决不同的问题,但会在同一条路由链路中协作。

通俗化理解“策略 + 算法”

  可以把它类比成物流系统:

  分片策略 = 物流规则,告诉系统:“我应该根据什么信息来分配?”

  例如:

  • Standard:根据一个字段
  • Complex:根据多个字段
  • Hint:根据业务外部提供的信息

  分片算法 = 具体计算方法,告诉系统:“拿到这个信息之后,到底怎么算?”

  例如:

  • 普通的单键取模:user_id % 4
  • 常见的日期范围:create_time → 按月份
  • 复杂的多键自定义:employee_id + create_time → 自定义复杂计算

  所以完整关系就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
        分片策略
│
“分片条件怎么获取/组织?”
│
↓
分片键/值
│
↓
分片算法
│
“具体怎么算?”
│
↓
实际数据节点

集成说明

添加依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.0</version>
</dependency>

修改配置文件

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# ---------------- 1. 数据源 ----------------
spring.shardingsphere.datasource.names=ds0,ds1
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://127.0.0.1:3306/gps_db_0?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://127.0.0.1:3306/gps_db_1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456

# ---------------- 2. 逻辑表(员工表 + 案例① = 单节点;案例②~⑥分片) ----------------
# 员工表:非分片(单节点 ds0),业务主键 INPUT
spring.shardingsphere.rules.sharding.tables.employee.actual-data-nodes=ds0.employee

# ==================== 案例1:单表纪元(单节点表,主键 MP 雪花) ====================
spring.shardingsphere.rules.sharding.tables.track_one_ds_one_tb_raw.actual-data-nodes=ds0.track_one_ds_one_tb_raw

# ==================== 案例2:分库分表(员工) ====================
# 库=员工%2;表=(员工/2)%4(解耦公式避免分片空洞)
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.actual-data-nodes=ds$->{0..1}.track_mul_ds_eid_mul_tb_eid_$->{0..3}
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.database-strategy.standard.sharding-column=employee_id
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.database-strategy.standard.sharding-algorithm-name=algo-db-eid
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.table-strategy.standard.sharding-column=employee_id
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.table-strategy.standard.sharding-algorithm-name=algo-case2-tbl
# (已由 default-key-generate-strategy 全局统一配置)
# spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.key-generate-strategy.column=id
# spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.key-generate-strategy.key-generator-name=snowflake

# ==================== 案例3:分库(员工) + 按日分表 ====================
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_day.actual-data-nodes=ds$->{0..1}.track_mul_ds_eid_mul_tb_day_$->{20260726..20260806}
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_day.database-strategy.standard.sharding-column=employee_id
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_day.database-strategy.standard.sharding-algorithm-name=algo-db-eid
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_day.table-strategy.standard.sharding-column=gps_time
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_day.table-strategy.standard.sharding-algorithm-name=algo-day-interval

# ==================== 案例4:单库分表(员工) ====================
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid.actual-data-nodes=ds0.track_one_ds_mul_tb_eid_$->{0..3}
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid.table-strategy.standard.sharding-column=employee_id
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid.table-strategy.standard.sharding-algorithm-name=algo-case4-tbl

# ==================== 案例5:单库按日分表 ====================
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_day.actual-data-nodes=ds0.track_one_ds_mul_tb_day_$->{20260726..20260806}
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_day.table-strategy.standard.sharding-column=gps_time
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_day.table-strategy.standard.sharding-algorithm-name=algo-day-interval

# ==================== 案例6:单库混合(员工桶 × 日期, 复合分片键) ====================
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid_day.actual-data-nodes=ds0.track_one_ds_mul_tb_eid_day_$->{0..1}_$->{20260726..20260806}
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid_day.table-strategy.complex.sharding-columns=employee_id,gps_time
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid_day.table-strategy.complex.sharding-algorithm-name=algo-case6-complex
# (已由 default-key-generate-strategy 全局统一配置)
# spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid_day.key-generate-strategy.column=id
# spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid_day.key-generate-strategy.key-generator-name=snowflake

# ---------------- 3. 分片算法 ----------------
# 员工取模分库(内置 INLINE)
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-db-eid.type=INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-db-eid.props.algorithm-expression=ds$->{employee_id % 2}
# 案例2 分表(自定义类:库表解耦公式 (employee_id/2)%4)
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case2-tbl.type=CLASS_BASED
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case2-tbl.props.strategy=STANDARD
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case2-tbl.props.algorithmClassName=com.example.gps.sharding.MulDsEidMulTbEidShardingAlgorithm
# 案例4 分表(自定义类:employee_id%4)
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case4-tbl.type=CLASS_BASED
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case4-tbl.props.strategy=STANDARD
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case4-tbl.props.algorithmClassName=com.example.gps.sharding.OneDsMulTbEidShardingAlgorithm
# 按日分表:内置 INTERVAL(ShardingSphere 5.2.1 实测可用),datetime-lower 与数据窗口对齐。
# (早期在 5.4.1 曾遇 datetime-lower 解析 bug,故曾回退自定义 DayShardingAlgorithm——该类仍保留作为教学参考)
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.type=INTERVAL
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-pattern=yyyy-MM-dd HH:mm:ss
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-lower=2026-07-26 00:00:00
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-upper=2026-08-07 00:00:00
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.sharding-suffix-pattern=yyyyMMdd
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-interval-unit=DAYS
# 案例6 复合分片(自定义类:员工桶 × 日期)
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case6-complex.type=CLASS_BASED
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case6-complex.props.strategy=COMPLEX
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case6-complex.props.algorithmClassName=com.example.gps.sharding.OneDsMulTbEidDayShardingAlgorithm

# ---------------- 4. 主键生成器(ShardingSphere SNOWFLAKE) ----------------
# 全局默认主键策略:分片规则内所有表统一用 snowflake 生成 id,
# 无需为每个案例单独配置(employee / track_one_ds_one_tb_raw 显式传 id,不受影响)。
spring.shardingsphere.rules.sharding.default-key-generate-strategy.column=id
spring.shardingsphere.rules.sharding.default-key-generate-strategy.key-generator-name=snowflake
spring.shardingsphere.rules.sharding.key-generators.snowflake.type=SNOWFLAKE
spring.shardingsphere.rules.sharding.key-generators.snowflake.props.worker-id=1

# ---------------- 5. 其它 ----------------
spring.shardingsphere.props.sql-show=true

踩坑记录

  • Guava 冲突
  • 配置文件各数据源必须指定 type
  • Druid starter 冲突,需去除
  • 需要额外引入以下依赖
1
2
3
4
5
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>10.0.16</version>
</dependency>

实战案例

  下面通过实战案例演示下,同一个业务(环卫工人 GPS 轨迹,按”某员工某天”查询),用 多种不同的表切分方案进行分库分表。

  具体演示见sharding-track项目。

「员工 id 分库分表」

  此案例下,既用员工 id 分库,又用员工 id 分表,两层都用员工 ID 的哈希取模:

  • 库 = HASH_MOD(employee_id, 2);表 = HASH_MOD(employee_id, 5)(每库 5 张,共 10 张)。
  • 注意:两层取模必须互质,这样 (库,表) 的 10 种组合都能出现,10 张表全部生效;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ==================== 案例2:分库分表(员工) ====================
# 库=HASH_MOD(employee_id,2);表=HASH_MOD(employee_id,5)
# 教学点:两层取模必须互质——gcd(2,5)=1,(库,表) 10 种组合都能出现,10 张表均匀;
# 若表层用 4 则 hash%2 与 hash%4 相关(hash%2 ≡ (hash%4)%2),8 张表只会用到 4 张(分片空洞)。
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.actual-data-nodes=ds$->{0..1}.track_mul_ds_eid_mul_tb_eid_$->{0..4}
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.database-strategy.standard.sharding-column=employee_id
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.database-strategy.standard.sharding-algorithm-name=algo-hash-eid-2
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.table-strategy.standard.sharding-column=employee_id
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_eid.table-strategy.standard.sharding-algorithm-name=algo-hash-eid-5

# 员工哈希取模分库/分表(内置 HASH_MOD;本版本对整数键落位等价于 值 % count,已实测验证)
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-hash-eid-2.type=HASH_MOD
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-hash-eid-2.props.sharding-count=2
# 案例② 表层:每库 5 张表(与库层 2 互质,消除分片空洞)
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-hash-eid-5.type=HASH_MOD
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-hash-eid-5.props.sharding-count=5

「员工分库 + 按日分表」

  此案例下,员工 id 分库,轨迹创建时间按日分表:

  • employee_id 定库(员工维度),gps_time 定日表(时间维度),两层各管一件事。
  • “某员工某天” = 1 库 1 张日表;”某员工近 N 天” = N 张日表
  • 生产价值:历史按天 DROP/归档,过期数据零成本清理;单张日表体量恒定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_day.actual-data-nodes=ds$->{0..1}.track_mul_ds_eid_mul_tb_day_$->{20260726..20260806}
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_day.database-strategy.standard.sharding-column=employee_id
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_day.database-strategy.standard.sharding-algorithm-name=algo-hash-eid-2
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_day.table-strategy.standard.sharding-column=gps_time
spring.shardingsphere.rules.sharding.tables.track_mul_ds_eid_mul_tb_day.table-strategy.standard.sharding-algorithm-name=algo-day-interval

spring.shardingsphere.rules.sharding.sharding-algorithms.algo-hash-eid-2.type=HASH_MOD
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-hash-eid-2.props.sharding-count=2

# 按日分表:内置 INTERVAL(ShardingSphere 5.2.1 实测可用),datetime-lower 与数据窗口对齐。
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.type=INTERVAL
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-pattern=yyyy-MM-dd HH:mm:ss
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-lower=2026-07-26 00:00:00
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-upper=2026-08-07 00:00:00
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.sharding-suffix-pattern=yyyyMMdd
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-interval-unit=DAYS

「单库员工 id 分表」

  此案例下,只有一个数据库,之后用员工 id 分多张表

1
2
3
4
5
6
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid.actual-data-nodes=ds0.track_one_ds_mul_tb_eid_$->{0..3}
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid.table-strategy.standard.sharding-column=employee_id
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid.table-strategy.standard.sharding-algorithm-name=algo-hash-eid-4

spring.shardingsphere.rules.sharding.sharding-algorithms.algo-hash-eid-4.type=HASH_MOD
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-hash-eid-4.props.sharding-count=4

「单库按时间一日一表」

  此案例下,只有一个数据库,之后用轨迹创建时间分多张表

  • 表 = gps_time 所在日,单库一日一表,表需要提前创建
  • 按天归档时,若”某员工某天”没有员工等值可路由,先按日期命中日表、再在表内过滤员工
1
2
3
4
5
6
7
8
9
10
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_day.actual-data-nodes=ds0.track_one_ds_mul_tb_day_$->{20260726..20260806}
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_day.table-strategy.standard.sharding-column=gps_time
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_day.table-strategy.standard.sharding-algorithm-name=algo-day-interval

spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.type=INTERVAL
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-pattern=yyyy-MM-dd HH:mm:ss
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-lower=2026-07-26 00:00:00
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-upper=2026-08-07 00:00:00
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.sharding-suffix-pattern=yyyyMMdd
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-day-interval.props.datetime-interval-unit=DAYS

「单库员工×日复合分片键分表」

  此案例下,只有一个数据库,但是分表时,复合处理:

  • 先按员工ID哈希取模分桶(5 个桶 → 每天 5 张表),再按日表定位,两个维度同时决定表。
  • 需要自定义复合分片算法(OneDsMulTbEidDayShardingAlgorithm,COMPLEX 策略)——
  • 单员工单日精确命中 1 张最小切片表:既有员工点查优势,又支持按天归档
1
2
3
4
5
6
7
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid_day.actual-data-nodes=ds0.track_one_ds_mul_tb_eid_day_$->{0..4}_$->{20260726..20260806}
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid_day.table-strategy.complex.sharding-columns=employee_id,gps_time
spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_eid_day.table-strategy.complex.sharding-algorithm-name=algo-case6-complex

spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case6-complex.type=CLASS_BASED
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case6-complex.props.strategy=COMPLEX
spring.shardingsphere.rules.sharding.sharding-algorithms.algo-case6-complex.props.algorithmClassName=com.example.gps.sharding.OneDsMulTbEidDayShardingAlgorithm

落地流程

  1. 确定分表规则并建表:规则为“5 个员工桶 × 每天一组表”,全部放在 ds0。每张表结构相同,新日期的表需要提前创建(可以通过定时任务处理)
  2. 实现复合分片接口选表:自定义类实现 ComplexKeysShardingAlgorithm<Comparable<?>>,核心是 doSharding()接收查询条件,返回目标物理表名集合
  3. 配置逻辑表和算法的关联:当前配置的关键项如下,完整属性见上面配置,主要为以下:
配置项 作用/当前值
actual-data-nodes 声明 ds0 下有哪些候选物理表
table-strategy.complex.sharding-columns 两个分片字段:employee_id,gps_time
table-strategy.complex.sharding-algorithm-name 关联 algo-case6-complex
算法的 type CLASS_BASED,通过 Java 类提供算法
算法的 props.strategy COMPLEX,使用复合分片策略
算法的 props.algorithmClassName 自定义算法类的完整类名
  1. 业务代码操作逻辑表:插入时传入员工 ID 和 GPS 时间,算法确定落在哪一张表;查询时根据已有条件确定一或多张表。实体上的 @TableName 使用逻辑表名
  2. 由框架完成实际执行:ShardingSphere 调用算法选表,然后把逻辑表名改写为物理表名,执行 SQL,并归并多表结果。自定义算法只负责选表。

核心代码分析

  doSharding() 的具体执行过程,可以按源码中的三个步骤理解。

  第一步,读取分片条件:

1
2
3
4
5
6
7
buckets    默认是 [0, 1, 2, 3, 4]
preciseDay 默认是 null
range 默认是 null

发现 employee_id 等值条件 → 计算桶号,放入 buckets
发现 gps_time 等值条件 → 提取日期,放入 preciseDay
发现 gps_time 范围条件 → 取出区间,放入 range

  第二步,匹配表名:

1
2
3
4
5
6
7
8
9
10
遍历选中的桶:
有 preciseDay:
直接拼出“表前缀_桶号_日期”

没有 preciseDay:
遍历 availableTargetNames 中的候选表
先检查表名前缀是否属于当前桶
再提取表名末尾日期
用 inRange() 判断该日期是否落在查询范围内
符合就加入结果

  范围查询时,代码是在遍历已有候选表并筛选日期。 如果没有时间条件,inRange(day, null) 返回 true,该桶所有可解析日期的表都会保留。

  第三步,把结果与 availableTargetNames 求交集后返回。这里检查的是“是否属于配置候选表”,不会查询 MySQL 来验证表是否真的存在。

不同查询场景命中的表

  以下用 T 代指 track_one_ds_mul_tb_eid_day,员工仍为 10006L,对应桶 1。

查询条件 员工桶怎么选 日期怎么选 命中的表
只查一个人,不限时间 桶 1 全部有效日期 T_1_各日期,演示窗口共 12 张
只查单日,不限员工 全部 5 桶 20260806 T_0_20260806 ~ T_4_20260806,5 张
只查最近两天 全部 5 桶 20260805、20260806 每天 5 张,共 10 张
只查最近一周 全部 5 桶 7 月 31 日~8 月 6 日 每天 5 张,共 35 张
单人当天 桶 1 20260806 T_1_20260806,1 张
单人最近两天 桶 1 20260805、20260806 T_1_20260805、T_1_20260806,2 张
单人最近一周 桶 1 7 月 31 日~8 月 6 日 桶 1 对应的 7 张日表

  可以直接记成:命中表数 = 选中的桶数 × 范围内已配置的有效日期数。

  只查一个人时,SQL 条件是WHERE employee_id = 10006

  代码把 buckets 从五个桶缩小成 [1];没有时间条件,保留桶 1 的所有日表。不同日期的数据分散在不同表里,因此只给员工 ID 还不能定位到一张表。

  只查当天时,SQL 条件应写成:

1
2
WHERE gps_time >= '2026-08-06 00:00:00'
AND gps_time < '2026-08-07 00:00:00'

  没有员工条件,保留五个桶。日期范围只覆盖 8 月 6 日,因此选出当天的五张表。

  查最近两天或一周时,只需改变开始时间:

1
2
3
4
5
6
7
-- 最近两天
WHERE gps_time >= '2026-08-05 00:00:00'
AND gps_time < '2026-08-07 00:00:00'

-- 最近一周
WHERE gps_time >= '2026-07-31 00:00:00'
AND gps_time < '2026-08-07 00:00:00'

  算法逐个检查候选表的日期,保留范围内的日表。单日和多日走的是同一段范围匹配代码,区别只是匹配的日期数量。

  单人当天、单人多日,则在上述时间条件前加上WHERE employee_id = 10006,这样桶集合缩小为 [1],日期集合保持不变,所以表数从“每天五张”降为“每天一张”。

  项目现有查询接口,实际走的就是“单人+日期范围”这条流程。

  DemoCaseService.doQuery() (line 201)的代码是:

1
2
3
4
5
6
7
LocalDateTime s = start.atStartOfDay();
LocalDateTime e = end.plusDays(1).atStartOfDay();

w.eq("employee_id", employeeId)
.ge("gps_time", s)
.lt("gps_time", e)
.orderByAsc("gps_time");

  例如传入:

1
2
3
employeeId = 10006
start = 2026-08-05
end = 2026-08-06

  完整执行过程就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
业务层生成 employee_id = 10006、gps_time >= 8月5日、gps_time < 8月7日
↓
ShardingSphere 提取员工等值条件和时间范围
↓
doSharding() 算出桶 1
↓
筛选桶 1 的候选表,保留 8月5日、8月6日
↓
返回两张物理表名
↓
ShardingSphere 改写 SQL,查询两张表
↓
MySQL 按原 WHERE 条件筛选数据
↓
ShardingSphere 按 gps_time 归并排序,返回业务层

条件提取逻辑

  在上面例子中进行分片算法代码逻辑处理中,ShardingSphere 内部:

  • 是怎么提取 employee_id 或 gps_time 分片条件呢?
  • 不同的=、IN、BETWEEN操作符又是分别怎么处理的?
  • 如果没提取到相关条件会怎样?

  这几个问题,我们下面分别回答。

  对这个项目的普通 SELECT,可以理解为:ShardingSphere 解析 SQL、整理分片条件,再交给重写的算法类选表。

  它根据配置认出 employee_id、gps_time 是分片字段;SQL 中直接写出的值直接读取,? 则从 JDBC 参数列表中按位置取值。

  传给自定义算法时,分为两个 Map:

1
2
3
4
5
// 离散值:=、IN
shardingValue.getColumnNameAndShardingValuesMap();

// 范围值:BETWEEN、>、>=、<、<=
shardingValue.getColumnNameAndRangeValuesMap();

= 提取为只有一个元素的值集合

  例如WHERE employee_id = 10006 AND gps_time = '2026-08-06 10:00:00'中传入的数据相当于:

1
2
3
4
5
valuesMap = {
employee_id: [10006],
gps_time: [2026-08-06 10:00:00]
}
rangeMap = {}

  类中读取员工值算出桶 1,读取时间得到日期 20260806,最终选中track_one_ds_mul_tb_eid_day_1_20260806

IN 提取为包含多个元素的值集合

  例如WHERE employee_id IN (10006, 10007)中传入的数据相当于:

1
2
3
4
valuesMap = {
employee_id: [10006, 10007]
}
rangeMap = {}

  类中读取员工值分别算出桶 1、2,取两个桶的表;没有时间条件,就覆盖这两个桶的全部日期。

  因此,框架会完整提取这些值,自定义算法需要把它们累积、去重后共同用于选表。

BETWEEN 提取为包含上下界的范围

  例如:

1
2
3
WHERE employee_id = 10006
AND gps_time BETWEEN '2026-08-05 00:00:00'
AND '2026-08-06 23:59:59'

  传入的数据相当于:

1
2
3
4
5
6
valuesMap = {
employee_id: [10006]
}
rangeMap = {
gps_time: [2026-08-05 00:00:00, 2026-08-06 23:59:59]
}

  方括号表示包含两端。框架传入的是区间,日期筛选由你的类完成:

1
2
3
员工 → 桶 1
时间范围 → 匹配 8月5日、8月6日
结果 → 桶 1 的两张日表

  如果写的是 employee_id BETWEEN 10006 AND 10010,框架同样能提取员工范围;但当前类只处理 rangeMap 中的 gps_time,所以员工范围不会缩小桶集合,仍保留五个桶。

未提取到相关条件时

  区分“缺一个”和“两个都缺”时,按下表说明处理

可用的分片条件 是否调用这个类 本项目的选表行为
只有员工等值条件 会 员工对应桶的全部有效日期表
只有时间条件 会 对应日期的全部五个桶
两个字段都没有可用条件 不会 框架直接选择全部配置表

  例如:

1
2
3
SELECT *
FROM track_one_ds_mul_tb_eid_day
WHERE speed > 10;

  speed 不是分片字段,没有可用分片条件。ShardingSphere此时的逻辑相当于:

1
2
3
目标表 = 分片条件为空
? 全部配置表
: 调用分片算法选表;

  所以会向全部配置表执行查询,再合并结果;speed > 10 仍交给 MySQL 筛选。**全路由表示所有分表都会参与查询,不等同于每张表一定执行全表扫描。

  结合项目当前的配置问题:全路由还会绕过这个类对非法日期表名的过滤,因此配置中的不存在表可能导致查询报错。

核心参数作用

  实现ComplexKeysShardingAlgorithm类时,需要重写doSharding方法,其中存在两个参数:

  • final Collection<String> availableTargetNames
  • final ComplexKeysShardingValue<Comparable<?>> shardingValue

  两个参数的关系是:拿着 shardingValue 中的条件,从 availableTargetNames 中挑选目标表。

参数 通俗含义 谁提供
availableTargetNames 本次允许选择的物理表名单 ShardingSphere 根据配置和路由上下文提供
shardingValue 本次 SQL 中提取出来的分片条件 ShardingSphere 解析 SQL、读取绑定参数后提供

  availableTargetNames 表示“有哪些表可选”。 在本项目中,它是 ds0 下的候选物理表名集合,例如:

1
2
3
4
5
6
7
8
[
"track_one_ds_mul_tb_eid_day_0_20260805",
"track_one_ds_mul_tb_eid_day_1_20260805",
"track_one_ds_mul_tb_eid_day_2_20260805",
...
"track_one_ds_mul_tb_eid_day_1_20260806",
...
]

  需要注意:

  • 每个元素是物理表名,不带 ds0. 前缀,也不是逻辑表名
  • 它来自 actual-data-nodes 等配置,不保证数据库里实际已经创建了这些表
  • 它是候选集合;最终应该查询哪些表,由当前算法继续筛选

  这个类对它有两种使用方式(一般我们使用第二种):

1
2
3
4
5
6
7
// 范围查询:遍历候选表,根据桶号和日期筛选
for (String name : availableTargetNames) {
...
}

// 精确值查询:拼好表名后,检查是否属于候选集合
.filter(availableTargetNames::contains)

  shardingValue 表示“这次根据什么条件选表”。 它封装了三个主要部分:

方法 内容 示例
getLogicTableName() 当前操作的逻辑表名 track_one_ds_mul_tb_eid_day
getColumnNameAndShardingValuesMap() =、IN 提取出的离散值 employee_id → [10006, 10007]
getColumnNameAndRangeValuesMap() BETWEEN、比较符提取出的范围 gps_time → [开始时间, 结束时间)

  例如业务执行:

1
2
3
4
5
SELECT *
FROM track_one_ds_mul_tb_eid_day
WHERE employee_id IN (10006, 10007)
AND gps_time >= '2026-08-05 00:00:00'
AND gps_time < '2026-08-07 00:00:00';

  那么 shardingValue 的内容可以理解为:

  • logicTableName:track_one_ds_mul_tb_eid_day
  • columnNameAndShardingValuesMap:employee_id → [10006, 10007]
  • columnNameAndRangeValuesMap:gps_time → [2026-08-05 00:00:00, 2026-08-07 00:00:00)

  上面的 [ 表示包含下界,) 表示不包含上界。Map 中的值保留原有类型;员工 ID 可能是 Long,时间可能是 LocalDateTime、Timestamp 等,所以使用 Comparable<?> 容纳不同分片字段的值。

  当前算法收到这些条件后:

1
2
10006、10007 → 桶集合 {1, 2}
时间范围 → 日期集合 {20260805, 20260806}

  再与候选表名单匹配,返回四张表:

1
2
3
4
track_one_ds_mul_tb_eid_day_1_20260805
track_one_ds_mul_tb_eid_day_1_20260806
track_one_ds_mul_tb_eid_day_2_20260805
track_one_ds_mul_tb_eid_day_2_20260806

  shardingValue 里没有整条 SQL,也没有查询结果;算法返回这些表名后,实际 SQL 改写、执行和结果归并由 ShardingSphere 完成。

  另外,参数前的 final 只表示方法内部不能给参数变量重新赋值,并不代表里面的集合或 Map 不可修改。

思考

集成后是否影响原有逻辑?

  项目目集成 Shardingsphere-jdbc 后,对于原先的单表有什么影响,比如查询这块,还是走的代表查询吗?

  集成 ShardingSphere-JDBC 后,原来的单表不会自动变成“分片表”。只要没有给这张表配置分片规则,它仍然按照单表方式访问。

  可以简单理解为:ShardingSphere-JDBC 是在原 JDBC 上增加了一层 SQL 解析 + 路由。没有分片规则的表,就直接路由到对应数据源执行。

原来的普通单表

  例如原来有 SQL 查询SELECT * FROM sys_user WHERE id = 1;,会通过数据库直接到sys_user

  现在,接入 ShardingSphere 后,逻辑变为这样:

1
2
3
4
5
6
7
8
9
10
11
业务代码
↓
ShardingSphere-JDBC
↓
判断 sys_user 是否配置了分片规则
↓
没有
↓
直接路由到对应数据源
↓
mysql.sys_user

  所以 SQL 本身基本不用修改。

如果是分片表

例如执行SELECT * FROM t_order WHERE order_id = 100;,如果有分了多张表:

1
2
3
4
5
t_order
├── t_order_0
├── t_order_1
├── t_order_2
└── t_order_3

  ShardingSphere 会根据分片规则计算:

1
2
3
4
5
order_id = 100
↓
分片算法
↓
t_order_0

  然后实际查询:

1
SELECT * FROM t_order_0 WHERE order_id = 100;

  这才涉及真正的分片路由。官方配置中也是通过 actualDataNodes、databaseStrategy、tableStrategy 等配置描述逻辑表和实际节点。

项目中通常会同时存在两类表

类型 示例 查询方式
单表 sys_user、sys_dict 直接路由到对应数据源
分片表 t_order_0 ~ t_order_15 根据分片键计算具体库表
广播表 sys_region 根据广播规则处理多个数据源

  ShardingSphere 官方也把没有进行分片的表称为 Single Table(单表)。

对原有业务最大的影响

  实际上主要是这几个方面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
原来的:

MyBatis
↓
DataSource
↓
MySQL


接入 ShardingSphere:

MyBatis
↓
ShardingSphereDataSource
↓
SQL解析
↓
┌──────────────┬──────────────┐
│ 单表 │ 分片表 │
│ 直接路由 │ 分片计算 │
└──────────────┴──────────────┘
↓
MySQL

  因此,如果项目只是新增 ShardingSphere-JDBC,同时原有大量普通表不参与分片,这些表的 SQL 一般不需要改,仍然是正常的单表查询。

  不过有一个需要注意的版本问题:ShardingSphere 5.4.0 起,Single Table 的加载方式发生了调整,需要通过 YAML 的 !SINGLE 或 DistSQL 显式加载单表,不能简单理解成所有数据库中的普通表都会被自动识别。

分库分表在 ShardingJDBC 中落地需要注意什么?

落地的关键,是让主要业务查询稳定地命中少量分片,同时保证数据查得全、规则可维护、以后能扩容。 结合这个轨迹案例,需要会重点关注以下几点。

  1. 先确认瓶颈,再决定怎么拆。
    先检查慢 SQL、索引和数据增长情况。单库分表可以减小单表规模,但仍共享数据库实例的 CPU、磁盘和连接资源;如果瓶颈已经在实例层面,就要进一步考虑分库分表。
  2. 根据“主要怎么查”设计分片键。
    如果是查询“某员工在一段时间内的轨迹”,采用单库分表“员工分桶+日期”。单人当天查 1 张表,单人七天查 7 张表。同一套分片规则,对不同查询的收益并不一样。
  3. 让业务 SQL 能提供有效的分片条件。
    线上接口可以要求提供员工和时间范围,并限制查询跨度。尽量直接写 gps_time >= ? AND gps_time < ?;把分片字段包在函数或计算表达式里,否则可能导致无法提取条件而全路由。
  4. 自定义算法首先保证“不漏查”。
    除了 =,还要覆盖 IN、范围、缺少某个字段、重复值以及时间边界,避免同一条数据写到一处、查到另一处。
  5. 配置、算法和物理表需保持一致。
    配置随声明了表,不代表数据库已经建好;算法返回了表名,也不代表表一定存在。桶数、表名表结构和索引需要同步管理。按天分表还要安排提前建表、更新节点配置和历史数据归档。本案例的跨月整数区间配置问题,就是这一层需要处理的问题。
  6. 提前评估跨分片操作的代价。
    跨表排序、聚合、深分页会增加查询和结果归并开销;跨库关联、跨库事务还需要额外设计,并核实所用版本的支持范围。主键也应保证跨分片唯一。分片之后,每张表内部仍然需要合适的索引,不能只关注路由到了几张表。
  7. 扩容方案要在定规则时就考虑。
    例如从 % 5 改成 % 10,部分员工会被算到新的桶,但旧数据还留在原表里。因此扩容涉及数据迁移、校验、切换和回滚,不能只修改一个桶数配置。还要观察实际数据是否倾斜:员工分得均匀,不代表每个员工上报的数据量相同。
  8. 验收要看真实 SQL 和真实数据。
    单元测试验证算法选表是否正确,数据库集成测试验证实际读写是否完整,压测验证性能收益。重点观察命中表数、各表数据量、慢查询、连接池和结果归并开销;本项目页面上的路由是推算结果,排查问题时还需要看实际执行日志。

简而言之:先围绕业务查询选择分片键,再用 ShardingJDBC 配置和算法完成路由,同时验证多值与时间边界的正确性,并配套建表、监控和扩容方案,让分库分表既能减少查询范围,也能长期稳定运行。

actual-data-nodes 最佳实践是怎样的?

默认的 Groovy Row Value Expression

1
actual-data-nodes: ds$->{0..1}.t_order_$->{0..3}

等价于:

1
2
3
4
5
6
7
8
9
ds0.t_order_0
ds0.t_order_1
ds0.t_order_2
ds0.t_order_3

ds1.t_order_0
ds1.t_order_1
ds1.t_order_2
ds1.t_order_3

多个 $->{} 会做笛卡尔积,也可以枚举:

1
actual-data-nodes: ds$->{[0,1]}.t_order_$->{[0,2,5]}

或者字符串:

1
actual-data-nodes: ds$->{['east','west']}.t_order_$->{1..3}

这种最适合:取模分库、取模分表、固定编号分片。

日期场景建议 INTERVAL

  INTERVAL 参数含义如下:

参数 含义 示例
P 表名前缀 ds0.track_
SP 日期后缀格式 yyyyMMdd
DIA 每隔多少单位 1
DIU 单位 Days
DL 开始日期 20260726
DU 结束日期 20260806

  如DIA=1和DIU=Days代表每 1 天生成一个 actualDataNode。

支持按天分表

  例如你只有一个库 ds0,按天建轨迹表:

1
2
3
4
track_20260726
track_20260727
...
track_20260806

  可以直接:

1
2
3
4
5
6
7
actual-data-nodes: >-
<INTERVAL>P=ds0.track_;
SP=yyyyMMdd;
DIA=1;
DIU=Days;
DL=20260726;
DU=20260806

  实际上配置一般写成一行:

1
actual-data-nodes: <INTERVAL>P=ds0.track_;SP=yyyyMMdd;DIA=1;DIU=Days;DL=20260726;DU=20260806

  ShardingSphere 会生成:

1
2
3
4
5
6
7
8
9
ds0.track_20260726
ds0.track_20260727
ds0.track_20260728
ds0.track_20260729
ds0.track_20260730
ds0.track_20260731
ds0.track_20260801
...
ds0.track_20260806

  这里它用的是真正的 Java 日期时间逻辑,而不是数字 20260726..20260806。官方支持日、月、年等时间间隔,并要求 SP 使用 DateTimeFormatter 格式。ShardingSphere

支持按月分表

  比如

  • track_202601
  • track_202602
  • …
  • track_202612

  可以配置:

1
actual-data-nodes: <INTERVAL>P=ds0.track_;SP=yyyyMM;DIA=1;DIU=Months;DL=202601;DU=202612

  得到:

1
2
3
4
ds0.track_202601
ds0.track_202602
...
ds0.track_202612

  官方文档也直接给了 yyyy_MM + Months 的示例。ShardingSphere

支持按年分表

  例如:

1
actual-data-nodes: <INTERVAL>P=ds0.track_;SP=yyyy;DIA=1;DIU=Years;DL=2026;DU=2036

  得到:

1
2
3
4
ds0.track_2026
ds0.track_2027
...
ds0.track_2036

注意事项

只支持单库

  当前官方 INTERVAL Row Value Expression 有一个明确限制:它生成的字符串只适用于单个真实数据库中的多张真实表。

  对于:多个真实数据库 + 多个真实表,官方建议实现自己的InlineExpressionParserSPI。

容易混淆的 INTERVAL

  ShardingSphere 中存在两个不同层面的 INTERVAL。

actualDataNodes 的 <INTERVAL>

  actual-data-nodes: <INTERVAL>P=...作用是生成有哪些表,例如:

1
2
3
track_20260726
track_20260727
track_20260728
shardingAlgorithms 的 INTERVAL

  例如:

1
2
3
4
5
6
7
8
9
10
shardingAlgorithms:
track-date:
type: INTERVAL
props:
datetime-pattern: yyyy-MM-dd HH:mm:ss
datetime-lower: 2026-01-01 00:00:00
datetime-upper: 2030-12-31 23:59:59
sharding-suffix-pattern: yyyyMMdd
datetime-interval-amount: 1
datetime-interval-unit: DAYS

  作用是:

1
2
3
4
5
6
7
8
9
10
SQL:
track_time = 2026-07-28 12:00:00

↓

INTERVAL 分片算法

↓

track_20260728

  也就是:决定 SQL 去哪张表。

  官方当前 INTERVAL 分片算法支持 datetime-pattern、上下界、后缀格式以及 DAYS/MONTHS 等时间单位。

对比

  两者名字一样,但是职责完全不同:

对比项 <INTERVAL> Row Expression INTERVAL ShardingAlgorithm
所属位置 actualDataNodes tableStrategy
核心作用 生成实际数据节点 计算 SQL 应该路由到哪张表
回答的问题 “有哪些表?” “去哪个表?”
关注点 物理表集合 路由规则
示例结果 track_20260726、track_20260727 gps_time=2026-07-27 → track_20260727
本质 定义分片拓扑 执行分片路由

适配场景

类型 示例 适合
LITERAL <LITERAL>ds0.t_1,ds0.t_2 明确列举实际节点
默认 GROOVY ds_${0..1}.t_${0..15} 取模、编号、枚举
INTERVAL <INTERVAL>P=ds0.t_;SP=yyyyMMdd;... 单库时间分表
自定义 SPI <MY_INTERVAL>... 多库 + 日期、多维复杂拓扑

  这些都是当前官方 Row Value Expression 体系的一部分;如果不写类型,默认按 Groovy 解析。

扩展──AUTO_INTERVAL(了解)

  ShardingSphere 甚至还有type: AUTO_INTERVAL,这是自动分片算法,需要配合autoTables使用。

  例如概念上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rules:
- !SHARDING
autoTables:
track:
actualDataSources: ds0
shardingStrategy:
standard:
shardingColumn: gps_time
shardingAlgorithmName: track-auto

shardingAlgorithms:
track-auto:
type: AUTO_INTERVAL
props:
datetime-lower: '2026-01-01 00:00:00'
datetime-upper: '2030-01-01 00:00:00'
sharding-seconds: 86400

  AUTO_INTERVAL 是 ShardingSphere 的自动分片算法,其根据时间范围和固定时间间隔自动计算分片的时间分片算法,适合按天、按小时等固定周期自动划分数据分片。

  具体而言,AUTO_INTERVAL 的内部逻辑不是根据gps_time的yyyyMMdd计算出track_20260726,而是判断gps_time - datetime-lower相差多少秒,之后除以sharding-seconds得到分片编号 N。

  当前源码就是通过Duration.between(dateTimeLower, dateValue)计算相对时间,再除以shardingSeconds最后得到一个数字分区号;精确路由和范围路由最后匹配的 suffix 都是数字。

  因此,其不是track_20260726、track_20260727这种日期分片,而是更接近track_0、track_1递增数字分片。

  另外需要注意的是:AUTO_INTERVAL 不会像定时任务一样,到了新的一天就自动执行 CREATE TABLE。 更准确地说:它属于 autoTables 自动分片体系,ShardingSphere 能根据算法自动计算物理分片;所以,我们确定好配置好规则后,通过 ShardingSphere 执行逻辑表的 CREATE TABLE,它可以把 DDL 下发到算法计算出的物理分片上。官方对 AutoTable 的建议也是“先配置规则,再执行 CREATE TABLE”。

文章信息

时间 说明
2026-04-22 初稿
0%