简介:面向Spring Boot开发者,这份资源针对ShardingSphere 5.2.1分表不分库场景,解决单表数据量增长带来的查询性能瓶颈。项目基于Spring Boot构建,代码量精炼,共16个文件,其中9个Java类负责分片规则构建、数据源装配与分库分表策略实现;2个YAML和2个XML主要承担分片策略、数据源及MyBatis映射配置;另有SQL建表脚本、分片算法定义等辅助文件,包体仅15KB,结构清晰简洁。内容覆盖范围分片、哈希分片与自定义分片算法的实际写法,同时涉及数据迁移、分页查询和分布式事务等落地细节,可帮助开发者快速掌握在Spring Boot中整合ShardingSphere 5.2.1的完整路径。已有1953人学习,特别适合已具备Spring Boot基础、准备升级数据层处理能力的开发者。通过这套工程,能省去从零搭建和排查配置的时间,直接对照源码验证自己的分表方案,为生产环境改造提供可复用的参考。
1. springboot 整合 ShardingSphere 5.2.1:这组关键词背后是大多数分库分表改造的起点
springboot 整合 ShardingSphere 5.2.1,这组关键词在分库分表的搜索场景里几乎天天出现。点进来的人大多不是想学概念,而是手上有一个单体应用正在被数据量逼到墙角:单表几千万行、写入变慢、查询超时,又不想一步跨到分库分表中间件集群,于是想在 springboot 项目里用最少代价把 ShardingSphere-JDBC 装进来。5.2.1 这个版本号则更有意思——很多博客把它标成“最新版”,但它更多是 ShardingSphere 5.x 早期配置体系趋于稳定的一个节点,教程存量大、坑也基本被踩熟了。这篇不打算复刻某个教程,而是以 5.2.1 为基线,讲清楚从依赖引入、YAML 配置、分片算法选型到读写分离、分布式事务边界的一整条落地路径,顺带把启动失败、分片没生效、数据错乱这几类高频故障的排查顺序整理出来。适合两类人:正在准备把单表拆成多表的 springboot 开发者,以及已经整合完但被配置细节绕晕的人。你在集成过程中踩过的坑大概率都在后面,直接照着核对就行。
2. 从依赖到启动:springboot 里的 ShardingSphere-JDBC 最小整合形态
2.1 ShardingSphere-JDBC 在 springboot 项目里扮演什么角色
先明确一个容易混淆的点:ShardingSphere 是一套体系,包含 JDBC、Proxy 两种形态。springboot 整合通常指的是 ShardingSphere-JDBC,它不是一个独立服务,而是一个 jar 包,嵌入在应用进程内部。应用拿到的 DataSource 实际上是被 ShardingSphere 包装过的逻辑数据源,SQL 经过解析、改写、路由之后,再分发到背后的真实数据库连接上执行。这个模式的好处是部署零额外成本,业务代码里继续用 JdbcTemplate、MyBatis-Plus 都没问题,坏处是应用自身承担了分片路由的计算开销,连接数也会被多分片放大。
| 对比项 | ShardingSphere-JDBC | ShardingSphere-Proxy |
|---|---|---|
| 部署方式 | 嵌入应用进程 | 独立服务,应用通过 JDBC 连接 |
| 改造成本 | 只改数据源配置和依赖 | 应用侧连地址,SQL 兼容性由 Proxy 承担 |
| 适合场景 | 中小团队、单应用、不想引入额外节点 | 多应用共享分片规则、异构语言接入 |
springboot 项目里整合 JDBC 形态,核心是让 starter 接管数据源装配。5.2.1 的 starter 会自动检测配置并生成逻辑 DataSource,这一步如果和 spring.datasource 自动装配冲突,就会出现启动报错——这个问题后面避坑章节专门讲。
2.2 最小可跑配置:pom 依赖与 application.yml 一次到位
先搭一个只分表不分库的最小工程,验证整合链路通不通。Maven 依赖只需要一个 starter:
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.2.1</version> </dependency>然后在 application.yml 里给出最简配置。这里我用一个物理库、4 张物理表来模拟订单表拆分:
spring: shardingsphere: datasources: ds_order: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db?useSSL=false&serverTimezone=Asia/Shanghai username: root password: root rules: sharding: tables: t_order: actual-data-nodes: ds_order.t_order_${0..3} table-strategy: standard: sharding-column: order_id sharding-algorithm-name: t_order_hash sharding-algorithms: t_order_hash: type: HASH_MOD props: sharding-count: 4 props: sql-show: false这段配置的逻辑拆开看:datasources 下定义的是真实物理数据源,这里只有一个 ds_order;rules.sharding.tables.t_order 定义逻辑表 t_order 到物理表的映射关系,actual-data-nodes 里的${0..3}是 ShardingSphere 的内联表达式,启动时会展开成 t_order_0、t_order_1、t_order_2、t_order_3;table-strategy 指定分片键是 order_id,分片算法名叫 t_order_hash;算法定义在 sharding-algorithms 里,类型 HASH_MOD,对分片键做哈希后对 4 取模,决定落在哪张物理表。
注意几个 5.2.1 的细节:配置前缀是 spring.shardingsphere,不是老教程里的 shardingsphere;HASH_MOD 的参数名是 sharding-count,不是早期 4.x 的 count;如果只分表不分库,actual-data-nodes 里数据源名可以直接写固定名字。启动时看到控制台打印出逻辑数据源创建成功,并且没有任何 DataSource 相关的报错,就说明整合链路已经通了。此时先不急着写业务代码,下一步把标准分库分表规则补上,才是真正能落地的形态。
3. 标准分片规则落库:从单表到分库分表的完整 YAML
3.1 分片键与分片算法选型:inline、hash_mod、interval 的适用边界
分片算法选型是整个分片规则里最不能照抄的部分。5.2.1 的内置算法类型不少,但实际项目里高频使用的就三类:INLINE、HASH_MOD、INTERVAL。INLINE 基于 Groovy 表达式做计算,配置灵活,适合分片键有明显业务含义、能用表达式直接算的场景,比如按 user_id 直接取模:
type: INLINE props: algorithm-expression: ds_${user_id % 2} allow-range-query-with-inline-sharding: false这里有个 5.2.1 经典坑:INLINE 默认禁止范围查询。如果业务 SQL 里出现 WHERE user_id BETWEEN 或者 WHERE user_id > 100,而 user_id 是分片键,ShardingSphere 会直接抛异常提示你开启 allow-range-query-with-inline-sharding。开了之后,范围查询会广播到所有分片执行,结果是正确的,但性能代价要自己评估。
HASH_MOD 是取模算法的哈希版本,它对分片键先做哈希再取模,避免字符串分片键直接取模带来的分布不均。INTERVAL 则按时间字段做区间分片,适合流水表按月份拆分的场景。三类算法的选择逻辑不复杂:分片键是整型、分布均匀,直接用 HASH_MOD;分片键有业务前缀、需要表达式参与,用 INLINE;分片键是时间字段,用 INTERVAL 加时间区间配置。不要一上来就堆复杂算法,能用一个取模解决的就不要引入额外依赖。
3.2 标准分库分表配置:一份能直接改的分片规则
分库分表的标准形态是两级路由:先按某个键分到库,再在库内分到表。下面这份配置是某零售订单系统的常见写法,两个库、每库 4 张订单表,分片键都用 order_id:
spring: shardingsphere: datasources: ds_0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db_0?useSSL=false&serverTimezone=Asia/Shanghai username: root password: root ds_1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db_1?useSSL=false&serverTimezone=Asia/Shanghai username: root password: root rules: sharding: tables: t_order: actual-data-nodes: ds_${0..1}.t_order_${0..3} database-strategy: standard: sharding-column: order_id sharding-algorithm-name: order_db_hash table-strategy: standard: sharding-column: order_id sharding-algorithm-name: order_table_hash key-generate-strategy: column: order_id key-generator-name: order_snowflake t_order_item: actual-data-nodes: ds_${0..1}.t_order_item_${0..3} database-strategy: standard: sharding-column: order_id sharding-algorithm-name: order_db_hash table-strategy: standard: sharding-column: order_id sharding-algorithm-name: order_table_hash binding-tables: - t_order, t_order_item sharding-algorithms: order_db_hash: type: HASH_MOD props: sharding-count: 2 order_table_hash: type: HASH_MOD props: sharding-count: 4 key-generators: order_snowflake: type: SNOWFLAKE props: sql-show: false这份配置里值得逐项说明的有几处。actual-data-nodes 的表达式是ds_${0..1}.t_order_${0..3},展开后是 ds_0.t_order_0、ds_0.t_order_1、ds_0.t_order_2、ds_0.t_order_3、ds_1.t_order_0 一直到 ds_1.t_order_3,总共 8 张物理表。分库算法 order_db_hash 对 2 取模,分表算法 order_table_hash 对 4 取模,最终一个 order_id 会确定唯一的物理位置。key-generate-strategy 用 SNOWFLAKE 生成分布式主键,避免多分片下自增主键冲突。
绑定表 binding-tables 是 join 查询性能的关键配置。t_order 和 t_order_item 使用相同的分片键、相同的分片算法,绑定之后,两表 join 只会在对应分片内执行,不会产生笛卡尔积跨分片关联。如果不配绑定表,order 和 order_item 的 join 会广播到所有分片组合,SQL 数量成倍增长。这个配置项很容易被忽略,等数据量上来、join 变慢的时候才会意识到。
物理表结构需要提前在 8 张表中手工建好,ShardingSphere 不会帮你建表。常见做法是在整合前写一份建表 SQL 脚本,用循环在四个库里各执行一遍。数据写入验证建议直接写一个集成测试:
@SpringBootTest class ShardingRouteTest { @Resource private JdbcTemplate jdbcTemplate; @Test void insertAndRoute() { jdbcTemplate.update("INSERT INTO t_order(order_id, user_id, amount) VALUES (?, ?, ?)", 1001L, 1L, new BigDecimal("99.00")); jdbcTemplate.update("INSERT INTO t_order(order_id, user_id, amount) VALUES (?, ?, ?)", 2002L, 2L, new BigDecimal("199.00")); List<Map<String, Object>> rows = jdbcTemplate.queryForList( "SELECT * FROM t_order WHERE order_id IN (1001, 2002)"); Assertions.assertEquals(2, rows.size()); } }这段测试的逻辑:插入两条订单,一条 order_id 是 1001,一条是 2002,然后按 order_id 查询。由于分片键出现在 WHERE 条件里,ShardingSphere 能精确路由到对应物理表,不会广播。如果这里查不到数据,优先检查 physical 表是否建全、分片算法取模结果和表序号是否对得上。这里要特别提醒:查询条件里没带分片键时,ShardingSphere 不会报错,而是默默广播到所有分片,然后把结果合并返回。很多人在这个点上产生误解,以为是查询走错了表,其实是广播执行了,日志里能看到真实 SQL 有多条。看日志的方法在最后一章讲。
4. 读写分离与跨库事务:5.2.1 里“整合”这个词的重量
4.1 读写分离配置:从零到一的主从路由
分库分表解决了单表数据量问题,但读多写少的业务很快会撞到另一个瓶颈:单库读压力过大。ShardingSphere-JDBC 的读写分离在 5.2.1 里的配置路径和分片规则是并列的,都在 rules 节点下。核心是先定义读写分离数据源,再把它作为分片规则里的数据源引用。
rules: readwrite-splitting: ><dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-xa-atomikos</artifactId> <version>5.2.1</version> </dependency>props: sql-show: false transaction-type: XAXA 的原理是两阶段提交,ShardingSphere 会把跨库操作纳入同一个全局事务,提交时协调所有分片。代价是事务时间变长、连接被长时间占用,并发一高就容易出现连接耗尽。另一个方向是接 Seata 做最终一致,需要额外部署 Seata 服务端,依赖也要换成 shardingsphere-transaction-base-seata-at,这个方案落地成本明显更高,但长事务场景下更友好。
我实际项目的选型经验是:90% 的业务能通过改造 SQL 避免跨库事务,比如把订单主表和明细表按同一个 order_id 分到同一分片,绑定表天然保证 local 事务内完成。真正需要 XA 或 Seata 的场景很少,不要在整合初期就把分布式事务当成必选项,先把本地事务的边界理清楚。
5. 5.2.1 整合避坑:启动失败、分片没生效与数据错乱的排查
5.1 启动阶段的三类故障
现象一:项目启动直接报No qualifying bean of type 'DataSource'或DataSource相关自动装配冲突。原因是 pom 里同时保留了 spring-boot-starter-jdbc 的自动数据源配置和 ShardingSphere 的逻辑数据源,两个 DataSource Bean 互相竞争。解决方法是排除 DataSource 自动装配,让 ShardingSphere 完全接管:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }现象二:启动时控制台报错,提示某个 sharding-algorithms 类型找不到。原因通常是两个不同版本的 ShardingSphere 依赖同时出现在依赖树里,比如某个中间件传递依赖引入 5.0.0,而你自己声明的 5.2.1 被它覆盖。解决方法是先用 mvn dependency:tree 查看依赖冲突,然后统一用 dependencyManagement 锁定所有 shardingsphere 相关依赖的版本。
现象三:启动成功,但一执行 SQL 就报表不存在,提示的是逻辑表名或物理表名找不到。原因分两种:一种是 actual-data-nodes 里的物理表没建,另一种是配置里的${0..3}被当成普通字符串没有展开。第二种情况多半发生在 YAML 的键值没有用双引号包裹的场景,比如${0..3}在部分 YAML 解析器里会被单引号或特殊字符干扰。解决方式是核对物理表是否存在,以及把表达式单独抽出来测试展开结果。
5.2 运行时的数据正确性与事务边界问题
现象四:配置了 INLINE 分片算法,执行范围查询时直接抛异常,提示开启 allow-range-query-with-inline-sharding。这个现象是 5.2.1 的预期行为,不是 bug。INLINE 算法为了防误用,默认关闭范围查询。按需在算法 props 里加上 allow-range-query-with-inline-sharding: true,开启后要清楚这意味着所有分片都会被扫描。更合理的选择是范围查询走专门设计的分片键,或在查询条件里同时带上分片键的等值条件。
现象五:方法标注了 @Transactional,但跨库更新后出现数据只写了一半。这个坑的根源有两个层次,第一层是 spring 事务的经典自调用问题:同类内部方法调用 this.xxx() 时,注解不经过代理对象,事务根本不会开启。第二层是即使事务开了,LOCAL 模式只保证单库事务,跨库的多个分片各自独立提交,任何一个失败都不会让其他分片回滚。解决方式:跨库场景先评估能否通过分片键设计收敛到单库事务;必须跨库的话按上一章说的切 XA 或 Seata。顺带提醒,hint 分片、上下文信息等 ThreadLocal 数据在线程池场景下不会自动透传,异步任务里使用分片时要显式传递分片上下文,否则路由会走默认广播。
现象六:配了 sql-show: true,但控制台看不到任何分片 SQL 日志。原因在于 5.2.1 的 SQL 日志走独立 logger,名字是 shardingsphere-sql,logback 默认不会把它打印到控制台。解决方式是在 logback 配置里显式加一个 logger,级别设 INFO:
<logger name="shardingsphere-sql" level="INFO"/>加了之后重启,每条逻辑 SQL 背后对应的真实物理表 SQL 就会打出来,这是排查分片是否生效最重要的手段。
6. 一个验证技巧:开启 sql-show 看清每条 SQL 真正的落库位置
最后分享一个我自己每次动分片规则都会先做的事:把 sql-show 打开,跑几条代表性 SQL,对照物理表核对落点。这个动作能避免绝大多数“配置对了但数据不知道写哪里”的焦虑。
配置开启方式很简单,就是把 props 里的 sql-show 设为 true,同时确认 logback 里 shardingsphere-sql 的 logger 是 INFO 级别。启动后插入一条订单,日志里会输出真实 SQL:
Actual SQL: ds_0 ::: INSERT INTO t_order_2 (order_id, user_id, amount) VALUES (?, ?, ?)这条日志说明 order_id 经过 HASH_MOD 路由后落在 ds_0 的 t_order_2。核对一下你的分片键值是否真的对应到这张表,就能验证算法配置是否符合预期。改动分片算法后,我还习惯用 Hint 方式强制指定分片值来绕过算法干扰,专门验证某一条数据是否被正确路由:
HintManager hintManager = HintManager.getInstance(); hintManager.addDatabaseShardingValue("t_order", 1L); hintManager.addTableShardingValue("t_order", 1L); try { // 强制将 order_id 路由到 ds_1 的 t_order_1 jdbcTemplate.queryForList("SELECT * FROM t_order WHERE order_id = ?", 100L); } finally { hintManager.close(); }这段代码里 HintManager 给 t_order 手工指定了库分片值和表分片值,查询会无视正常的分片算法,直接走指定分片。它在验证分片规则、排查路由异常时特别好用,但要注意用完必须 close,否则 ThreadLocal 里的值会污染后续 SQL 的路由结果。我现在的习惯是:每次改完分片算法,先开 sql-show 跑三条 SQL——一条精确等值查询、一条不带分片键的查询、一条 join 查询,逐条核对真实落库位置和日志输出,确认无误再关掉。这个习惯帮我避免过好几次上线后才发现规则写反的翻车,希望帮到你。
本文还有配套的精品资源,点击获取