ShardingJDBC 有哪些分片策略,你用的哪个?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
-
基础掌握度:不光考配置文件怎么写,重点是分片策略(
ShardingStrategy)和分片算法(ShardingAlgorithm)能不能分清楚,5 种策略各自的定位有没有数。 -
实践选型能力:有没有真的在项目里用过,能不能根据业务特点(查询模式、数据量、是否带分片键)做选型,而不是随便配一个就上线。
-
底层理解:每种策略背后的算法接口(
PreciseShardingAlgorithm、RangeShardingAlgorithm这些)能不能讲清楚,以及它们对 SQL 操作符的支持范围。
核心答案
ShardingJDBC 内置了 5 种分片策略,说白了都是 "分片键 + 分片算法" 的不同组合,区别就在于支持几个键、用哪类算法:
| 分片策略 | 实现类 | 分片键数量 | 适用场景 |
|---|---|---|---|
| 行表达式分片 | InlineShardingStrategy |
单键 | 简单的取模/区间,配置一行搞定 |
| 标准分片策略 | StandardShardingStrategy |
单键 | 需要 =/IN + 范围查询 |
| 复合分片策略 | ComplexShardingStrategy |
多键 | 多个字段联合分片 |
| Hint 分片策略 | HintShardingStrategy |
不走 SQL | 分片键不在 SQL 里 |
| 不分片策略 | NoneShardingStrategy |
无 | 单表,不分片 |
我自己实际用得最多的就是行表达式分片策略(InlineShardingStrategy),订单表用 user_id 取模,简单粗暴,业务查询基本都带 user_id,完全够用。复杂业务才上复合分片。
深度解析
一、先搞清楚两个核心概念:分片键 vs 分片算法
很多人把分片策略、分片键、分片算法三个概念搅在一起,这块当年我自己也是绕了好久才理清。
- 分片键(Sharding Key):数据库里某一列(或几列),用来决定数据落到哪个库哪个表。比如
user_id。 - 分片算法(Sharding Algorithm):具体怎么算的逻辑,比如
user_id % 2。 - 分片策略(Sharding Strategy):把分片键和分片算法绑在一起的封装,决定一条 SQL 怎么路由。
上图就是三者的关系:策略是外壳,算法才是核心,分片键是输入参数。面试官如果追问 "策略和算法有啥区别",你就这么答。
二、5 种分片策略逐个说
1. 行表达式分片策略(InlineShardingStrategy)
最常用、最简单的一种。用一行 Groovy 表达式搞定分片,不用自己写算法类。
# 用 user_id % 2 决定落到哪个库、哪个表
shardingRule:
tables:
t_order:
actualDataNodes: ds_${0..1}.t_order_${0..1}
databaseStrategy:
inline:
shardingColumn: user_id
algorithmExpression: ds_${user_id % 2}
tableStrategy:
inline:
shardingColumn: user_id
algorithmExpression: t_order_${user_id % 2}
适合:取模、按区间这种简单场景,90% 的中小项目用这个就够了。
坑:不支持范围查询(BETWEEN、>、<),只能处理 = 和 IN。原因是 Groovy 行表达式本质上只能算单值映射,没法根据一个区间反推出多个目标库表,遇到范围条件直接全路由广播。
2. 标准分片策略(StandardShardingStrategy)
支持单分片键,提供两个算法接口(4.x 及之前的写法):
PreciseShardingAlgorithm(必填):处理=、INRangeShardingAlgorithm(可选):处理BETWEEN、>、<、>=、<=
业务里有按时间范围查订单的需求(WHERE create_time BETWEEN ? AND ?),行表达式就搞不定了,得上标准分片策略。
补充一句:5.x 之后这俩接口合并成了统一的
StandardShardingAlgorithm,一个类里同时实现精确分片和范围分片两个doSharding重载,通过 SPI 机制注册,配置时用type引用算法。还内置了MOD、HASH_MOD、VOLUME_RANGE、AUTO_INTERVAL等一堆开箱即用的算法,简单场景配置就能用,不用再手写代码。
public class OrderPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<Long> shardingValue) {
// 按 user_id 取模路由
long userId = shardingValue.getValue();
for (String target : availableTargetNames) {
if (target.endsWith(String.valueOf(userId % 2))) {
return target;
}
}
throw new UnsupportedOperationException("路由失败");
}
}
3. 复合分片策略(ComplexShardingStrategy)
多个分片键同时参与路由,比如 user_id + create_time 联合分片。两个键的值都传给 ComplexKeysShardingAlgorithm,你自己决定怎么组合。
public class OrderComplexAlgorithm implements ComplexKeysShardingAlgorithm<Comparable<?>> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
ComplexKeysShardingValue<Comparable<?>> shardingValue) {
// 同时拿到 user_id 和 create_time 两个分片键的值
Map<String, Collection<Comparable<?>>> map = shardingValue.getColumnNameAndShardingValuesMap();
// 自己写组合逻辑...
return targets;
}
}
适合:数据量超大、查询维度多、单键分片搞不定的场景。说实话,小项目用不上。
4. Hint 分片策略(HintShardingStrategy)
特殊场景:SQL 里压根没有分片键,但你又想让数据落到指定库表。用 HintManager 在代码里手动塞分片值,底层走 ThreadLocal,只在当前线程生效。
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.addDatabaseShardingValue("t_order", 1L);
hintManager.addTableShardingValue("t_order", 1L);
// 这条 SQL 没带 user_id,但会按上面塞的值路由
List<Order> list = orderMapper.selectAll();
}
// HintManager 实现了 AutoCloseable,try-with-resources 自动清理
典型场景:后台管理系统的全表扫描,或者一些没法带分片键的复杂报表查询。注意用完一定要清理,不然 ThreadLocal 会内存泄漏。
5. 不分片策略(NoneShardingStrategy)
顾名思义,不分片。整个 ShardingJDBC 里这张表就是个普通单表,所有 SQL 直接发到默认数据源。一般用来做对照测试,或者把没分片的小表放进来一起管。
三、我自己项目里怎么选的
订单场景实战:
- 分片键选
user_id:因为 C 端用户查自己的订单,WHERE user_id = ?占了 95% 以上的流量,带分片键的查询不会触发广播路由(全库扫描)。 - 分片策略用
InlineShardingStrategy:配置简单,ds_${user_id % 4}+t_order_${user_id % 8},4 库 8 表,业务查询全走精确路由。 - 范围查询怎么办:报表和后台的按时间扫描,单独走从库或者 ES,不依赖 ShardingJDBC 路由。
这里有个核心原则:分片键必须覆盖最高频的查询路径。分片键选错了,后面所有查询都广播,ShardingJDBC 反而成了累赘。
面试高频追问
- 分片键怎么选?
- 选查询最频繁、且值分布均匀的字段。订单选
user_id,日志选create_time+id,千万别选性别这种基数太低的字段。
- 查询不带分片键会发生什么?
- 会触发全路由(广播),SQL 发到所有分片执行,结果合并返回。性能很差,生产环境要避免。
InlineShardingStrategy为什么不支持范围查询?
- Groovy 表达式只能算单值映射,没法根据一个区间推导出多个目标库表。需要范围查询就上
StandardShardingStrategy配RangeShardingAlgorithm。
- ShardingJDBC 5.x 和 4.x 分片算法有变化吗?
- 变化挺大。5.x 把原来分散的
PreciseShardingAlgorithm、RangeShardingAlgorithm等接口合并成了统一的StandardShardingAlgorithm,一个类同时实现精确 + 范围两个doSharding方法,通过 SPI 注册,用type标识。还内置了MOD、HASH_MOD、VOLUME_RANGE、AUTO_INTERVAL等一堆开箱即用的算法,简单场景配置就能用,不用写代码。
常见面试变体
- "ShardingJDBC 怎么决定一条 SQL 路由到哪个库?"
- "分片键选错了会有什么后果?"
- "Hint 分片用在哪?"
- "复合分片和标准分片啥区别?"
记忆口诀
5 种策略记法:一个简单(Inline)、一个标准(Standard)、一个复合(Complex)、一个手动(Hint)、一个不分(None)。从简单到复杂,按需求升级。
总结
ShardingJDBC 这 5 种分片策略,说白了就是把 "分片键 + 算法" 包了一层。中小项目用行表达式分片策略最省事,有范围查询需求上标准分片,多键联合走复合分片,SQL 不带分片键用 Hint。选型就一句话:让分片键对齐最高频的查询路径,不然全是坑。
