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+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. 基础掌握度:不光考配置文件怎么写,重点是分片策略(ShardingStrategy)和分片算法(ShardingAlgorithm)能不能分清楚,5 种策略各自的定位有没有数。

  2. 实践选型能力:有没有真的在项目里用过,能不能根据业务特点(查询模式、数据量、是否带分片键)做选型,而不是随便配一个就上线。

  3. 底层理解:每种策略背后的算法接口(PreciseShardingAlgorithmRangeShardingAlgorithm 这些)能不能讲清楚,以及它们对 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(必填):处理 =IN
  • RangeShardingAlgorithm(可选):处理 BETWEEN><>=<=

业务里有按时间范围查订单的需求(WHERE create_time BETWEEN ? AND ?),行表达式就搞不定了,得上标准分片策略。

补充一句:5.x 之后这俩接口合并成了统一的 StandardShardingAlgorithm,一个类里同时实现精确分片和范围分片两个 doSharding 重载,通过 SPI 机制注册,配置时用 type 引用算法。还内置了 MODHASH_MODVOLUME_RANGEAUTO_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 路由。

Inline 精确路由
Inline 精确路由

这里有个核心原则:分片键必须覆盖最高频的查询路径。分片键选错了,后面所有查询都广播,ShardingJDBC 反而成了累赘。

面试高频追问

  1. 分片键怎么选?
  • 选查询最频繁、且值分布均匀的字段。订单选 user_id,日志选 create_time+id,千万别选性别这种基数太低的字段。
  1. 查询不带分片键会发生什么?
  • 会触发全路由(广播),SQL 发到所有分片执行,结果合并返回。性能很差,生产环境要避免。
  1. InlineShardingStrategy 为什么不支持范围查询?
  • Groovy 表达式只能算单值映射,没法根据一个区间推导出多个目标库表。需要范围查询就上 StandardShardingStrategyRangeShardingAlgorithm
  1. ShardingJDBC 5.x 和 4.x 分片算法有变化吗?
  • 变化挺大。5.x 把原来分散的 PreciseShardingAlgorithmRangeShardingAlgorithm 等接口合并成了统一的 StandardShardingAlgorithm,一个类同时实现精确 + 范围两个 doSharding 方法,通过 SPI 注册,用 type 标识。还内置了 MODHASH_MODVOLUME_RANGEAUTO_INTERVAL 等一堆开箱即用的算法,简单场景配置就能用,不用写代码。

常见面试变体

  • "ShardingJDBC 怎么决定一条 SQL 路由到哪个库?"
  • "分片键选错了会有什么后果?"
  • "Hint 分片用在哪?"
  • "复合分片和标准分片啥区别?"

记忆口诀

5 种策略记法:一个简单(Inline)、一个标准(Standard)、一个复合(Complex)、一个手动(Hint)、一个不分(None)。从简单到复杂,按需求升级。

总结

ShardingJDBC 这 5 种分片策略,说白了就是把 "分片键 + 算法" 包了一层。中小项目用行表达式分片策略最省事,有范围查询需求上标准分片,多键联合走复合分片,SQL 不带分片键用 Hint。选型就一句话:让分片键对齐最高频的查询路径,不然全是坑。