序言
随着业务数据量不断增长,单表数据量过大容易带来索引膨胀、查询变慢、写入压力集中等问题。当数据规模达到一定程度后,分库分表就成为常见的数据库水平扩展方案。
但真正开始接触 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 | # 作用的库和表 |
五种分片策略
标准分片策略
标准分片策略(standard)适用于具有单一分片键的标准分片场景。
standard 策略支持精确分片,即在 SQL 中包含=、in 操作符,以及范围分片,包括 BETWEEN AND、>、<、>=、<= 等范围操作符。
该策略下有两个属性,分片字段 shardingColumn 和分片算法名 shardingAlgorithmName。
1 | spring: |
行表达式分片策略
行表达式分片策略(inline)适用于具有单一分片键的简单分片场景,支持 SQL 语句中 =、IN 操作符。
inline 策略支持在配置属性 algorithm-expression 中书写 Groovy 表达式,用来定义对分片健的运算逻辑,无需单独定义分片算法。
1 | spring: |
复合分片策略
复合分片策略(complex)适用于多个分片键的复杂分片场景,属性 shardingColumns 中多个分片健以逗号分隔。支持 SQL 语句中 >、>=、<=、<、=、IN 和 BETWEEN AND 等操作符。
比如:我们希望通过 user_id 和 order_id 等多个字段共同运算得出数据路由到具体哪个分片中,就可以应用该策略。
1 | spring: |
Hint 分片策略(了解)
Hint 强制分片策略相比于其他几种分片策略稍有不同,该策略无需配置分片健,由外部指定分库和分表的信息,可以让 SQL 在指定的分库、分表中执行。
使用场景:
- 强制在指定数据库进行某些数据操作
- 分片字段不存在 SQL 和数据库表结构中,而存在于外部业务逻辑
比如,我们希望用 user_id 做分片健进行路由订单数据,但是 t_order 表中也没 user_id 这个字段啊,这时可以通过 Hint API 手动指定分片库、表等信息,强制让数据插入指定的位置。
不分片策略
不分片策略,字如其意,如果设置了该策略,那么对逻辑表的所有操作将会执行全库表路由。具体配置属性如下:
spring.shardingsphere.rules.sharding.tables.xxx.database-strategy:nonespring.shardingsphere.rules.sharding.tables.xxx.table-strategy:none
不同分片策略下的路由流程
1 | SQL |
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 | SQL / Hint |
例如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 | CREATE TABLE order ( |
如果执行 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 | SELECT * |
这时候路由需要考虑:
1 | employee_id = 1001 |
此时,两个分片键承担不同作用:
employee_id:决定数据库create_time:决定具体月份/日期表
所以算法需要同时获得:
1 | { |
然后由复杂分片算法自己决定:
1 | ds_1 |
这就是 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_id1
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 | 1000 |
因此 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 | StandardShardingStrategyConfiguration( |
也就是说,Standard Strategy 本身就知道:精确算法是谁、范围算法是谁,例如:
1 | ShardingStrategy |
配置也比较直接:
1 | standard: |
官方 4.1 配置就是这种模式:precise-algorithm-class-name 对应 PreciseShardingAlgorithm,range-algorithm-class-name 对应 RangeShardingAlgorithm。
4.x 的问题:算法和 Strategy 耦合比较明显
假设:
1 | Standard Strategy |
如果以后又增加一种算法:HashAlgorithm、IntervalAlgorithm……
,Strategy 层就需要越来越多地认识各种算法接口,所以整体结构容易变成:
1 | Strategy |
这会让:“分片策略怎么组织路由”和:“具体算法怎么计算分片”之间产生比较强的耦合。
5.x 的核心变化:统一成 ShardingAlgorithm
到了 ShardingSphere 5.x,核心抽象变成org.apache.shardingsphere.sharding.spi.ShardingAlgorithm,于是结构变成:
1 | ShardingAlgorithm |
这时候 Standard Strategy 不再需要:“我要找一个 PreciseShardingAlgorithm”、“我要找一个 RangeShardingAlgorithm”,而是:“我有一个 shardingAlgorithmName,去找到这个算法。”
例如:
1 | standard: |
然后:
1 | sharding-algorithms: |
先给结论:3.x/4.x 更像是“策略直接持有具体算法对象/算法类”;5.x 更像是“策略只引用一个算法名称 → 算法注册中心根据 type 找到实现 → 初始化算法 → Strategy 调用统一的 ShardingAlgorithm 接口”。
4.x 到 5.x 配置的重大变化
以前重点是:配置 → Java Class:
1 | standard: |
现在先配置策略:
1 | standard: |
然后配置算法:
1 | sharding-algorithms: |
重点变成:Strategy → 算法名称 → 算法配置 → type → SPI → 具体算法实现
这就是 5.x 很重要的架构变化。
不同版本对 Precise 和 Range的影响
以前:
1 | Standard Strategy |
现在层级抽离为传递参数,不同算法分别处理这两类参数:
1 | Standard Strategy |
也就是说: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 | standard: |
算法:
1 | worker-db-inline: |
然后执行 SQL SELECT * FROM gps_track WHERE worker_id = 10001;
整个过程逻辑如下:
1 | ① Strategy |
所以:
- Standard → 决定“怎么组织分片”
- Precise → 描述“这次条件是什么样”
- INLINE → 决定“具体怎么算”
第一维:Standard Strategy —— “怎么组织分片”
首先看Standard Strategy,它属于分片策略 Strategy,它主要解决的问题是:“我要根据哪个分片键进行标准分片,以及如何把 SQL 中的分片条件交给算法。”
例如:
1 | standard: |
这里 Standard 告诉 ShardingSphere:我的分片键为worker_id、我的算法为worker-db-inline,所以 Standard 更像一个路由组织者 / 调度者。它自己不是10001 % 4,也不是10000 ~ 20000,它只是负责把这些信息组织起来。
第二维:Precise / Range —— “这次 SQL 给了什么条件”
这是另外一个层次。
例如WHERE worker_id = 10001的 SQL 给出了一个明确的值,所以形成PreciseShardingValue,可以理解成:
1 | 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 | type: INLINE |
于是:
1 | 10001 |
所以 Algorithm 回答的是“给我分片条件,我如何计算目标数据库/表?”
算法中的代码处理
以取模分片算法ModShardingAlgorithm为例子,其存在两个方法分别处理精确值和范围区间:
1 | public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Comparable<?>> shardingValue) { |
小节
| 概念 | 所属 | 回答的问题 |
|---|---|---|
| Standard | Strategy | 怎么组织分片和路由 |
| Precise / Range | 分片条件类型 | 这次 SQL 给的是单值还是范围 |
| Algorithm | ShardingAlgorithm | 根据分片条件怎么算目标分片 |
可以把它们看成:
1 | SQL |
某种程度上,它们并不是完全“独立”平行的“三个维度”,而是一个调用链上的三个层次:选择 Strategy → 匹配分片条件 → 传递给 Algorithm 计算确定路由
1 | 第一步:Strategy |
所以“不同维度”更准确地说是:它们解决不同的问题,但会在同一条路由链路中协作。
通俗化理解“策略 + 算法”
可以把它类比成物流系统:
分片策略 = 物流规则,告诉系统:“我应该根据什么信息来分配?”
例如:
- Standard:根据一个字段
- Complex:根据多个字段
- Hint:根据业务外部提供的信息
分片算法 = 具体计算方法,告诉系统:“拿到这个信息之后,到底怎么算?”
例如:
- 普通的单键取模:
user_id % 4 - 常见的日期范围:
create_time→ 按月份 - 复杂的多键自定义:
employee_id + create_time→ 自定义复杂计算
所以完整关系就是:
1 | 分片策略 |
集成说明
添加依赖
1 | <dependency> |
修改配置文件
1 | # ---------------- 1. 数据源 ---------------- |
踩坑记录
- Guava 冲突
- 配置文件各数据源必须指定 type
- Druid starter 冲突,需去除
- 需要额外引入以下依赖
1 | <dependency> |
实战案例
下面通过实战案例演示下,同一个业务(环卫工人 GPS 轨迹,按”某员工某天”查询),用 多种不同的表切分方案进行分库分表。
具体演示见sharding-track项目。
「员工 id 分库分表」
此案例下,既用员工 id 分库,又用员工 id 分表,两层都用员工 ID 的哈希取模:
- 库 = HASH_MOD(employee_id, 2);表 = HASH_MOD(employee_id, 5)(每库 5 张,共 10 张)。
- 注意:两层取模必须互质,这样 (库,表) 的 10 种组合都能出现,10 张表全部生效;
1 | # ==================== 案例2:分库分表(员工) ==================== |
「员工分库 + 按日分表」
此案例下,员工 id 分库,轨迹创建时间按日分表:
- employee_id 定库(员工维度),gps_time 定日表(时间维度),两层各管一件事。
- “某员工某天” = 1 库 1 张日表;”某员工近 N 天” = N 张日表
- 生产价值:历史按天 DROP/归档,过期数据零成本清理;单张日表体量恒定。
1 | 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} |
「单库员工 id 分表」
此案例下,只有一个数据库,之后用员工 id 分多张表1
2
3
4
5
6spring.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 | spring.shardingsphere.rules.sharding.tables.track_one_ds_mul_tb_day.actual-data-nodes=ds0.track_one_ds_mul_tb_day_$->{20260726..20260806} |
「单库员工×日复合分片键分表」
此案例下,只有一个数据库,但是分表时,复合处理:
- 先按员工ID哈希取模分桶(5 个桶 → 每天 5 张表),再按日表定位,两个维度同时决定表。
- 需要自定义复合分片算法(
OneDsMulTbEidDayShardingAlgorithm,COMPLEX 策略)—— - 单员工单日精确命中 1 张最小切片表:既有员工点查优势,又支持按天归档
1 | 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} |
思考
集成后是否影响原有逻辑?
项目目集成 Shardingsphere-jdbc 后,对于原先的单表有什么影响,比如查询这块,还是走的代表查询吗?
集成 ShardingSphere-JDBC 后,原来的单表不会自动变成“分片表”。只要没有给这张表配置分片规则,它仍然按照单表方式访问。
可以简单理解为:ShardingSphere-JDBC 是在原 JDBC 上增加了一层 SQL 解析 + 路由。没有分片规则的表,就直接路由到对应数据源执行。
原来的普通单表
例如原来有 SQL 查询SELECT * FROM sys_user WHERE id = 1;,会通过数据库直接到sys_user
现在,接入 ShardingSphere 后,逻辑变为这样:
1 | 业务代码 |
所以 SQL 本身基本不用修改。
如果是分片表
例如执行SELECT * FROM t_order WHERE order_id = 100;,如果有分了多张表:
1 | t_order |
ShardingSphere 会根据分片规则计算:
1 | order_id = 100 |
然后实际查询:
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 | 原来的: |
因此,如果项目只是新增 ShardingSphere-JDBC,同时原有大量普通表不参与分片,这些表的 SQL 一般不需要改,仍然是正常的单表查询。
不过有一个需要注意的版本问题:ShardingSphere 5.4.0 起,Single Table 的加载方式发生了调整,需要通过 YAML 的 !SINGLE 或 DistSQL 显式加载单表,不能简单理解成所有数据库中的普通表都会被自动识别。
文章信息
| 时间 | 说明 |
|---|---|
| 2026-04-22 | 初稿 |