ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot整合ShardingSphere 5.2.1:分库分表实战与避坑指南

SpringBoot整合ShardingSphere 5.2.1:分库分表实战与避坑指南 简介这份资源是面向Java后端开发者的SpringBoot整合ShardingSphere 5.2.1实战工程聚焦「分表不分库」这一常见大数据量优化场景适合已掌握SpringBoot基础、希望快速落地单库水平分表方案的中级开发者参考。压缩包共16个文件约15KB以9个Java源码为主体配合2个yml配置、2个xml映射文件以及建表sql、分片算法文件和gitignore等辅助内容完整呈现依赖引入、分片规则配置、数据源与事务管理等关键环节。资源围绕范围分片、哈希分片、虚拟节点及自定义算法等策略展开并涉及数据迁移、SQL优化、分页查询与分布式事务等注意事项便于读者对照代码理解分表配置的落地方式。目前已有1950人学习下载可作为搭建分表环境的起步模板帮助减少配置试错成本。1. SpringBoot 整合 ShardingSphere 5.2.1从单库到分片的临界点在哪订单表刚过 800 万行单表查询开始出现 1.2 秒以上的毛刺加索引、加从库都压不住写入热点——这是我第一次认真考虑分库分表的场景。ShardingSphere 5.2.1 是 Apache 顶级项目里比较成熟的一个版本它把分片、读写分离、数据加密、影子库这些能力统一收进一套 JDBC 增强里对 SpringBoot 项目来说改造成本主要集中在配置和 SQL 兼容性上而不是重写 DAO 层。这篇笔记面向已经有一个能跑的 SpringBoot 项目、正在评估要不要上分片的同学也适合已经决定用 ShardingSphere 但卡在 5.x 配置写法上的人。我会按「先想清楚为什么分、再动手配、最后踩坑」的顺序讲所有配置和代码都以 5.2.1 的实际行为为准。2. 分片之前先算账ShardingSphere 5.2.1 的定位与选型理由2.1 为什么是 ShardingSphere 而不是手写路由很多团队第一反应是在应用层用 ThreadLocal 存分片键然后手动拼表名。这种做法在只有两张分表时能跑但一旦分片键出现在 JOIN、子查询、聚合函数里手写路由的复杂度会指数级上升。ShardingSphere 的核心价值在于它把分片规则从业务代码里抽出来交给 SQL 解析器统一处理应用层看到的仍然是一张逻辑表。5.2.1 这个版本有几个关键特性需要先明确它同时支持 ShardingSphere-JDBC 和 ShardingSphere-Proxy 两种形态SpringBoot 项目通常用 JDBC 形态因为不需要额外部署进程配置方式上5.x 相比 4.x 最大的变化是引入了rules和dataSources的分离结构网上大量 4.x 的教程直接套到 5.2.1 会报配置解析错误。另外 5.2.1 对 SpringBoot 的自动配置支持已经比较完整shardingsphere-jdbc-core-spring-boot-starter这个 starter 能省掉手动创建 DataSource 的步骤。选型时还要考虑一个现实问题ShardingSphere 不是银弹。它解决的是数据量增长带来的单库瓶颈但如果你的瓶颈是复杂分析查询、跨片事务那引入它反而会增加复杂度。我一般建议先做容量评估单表超过 500 万行且写入 QPS 持续超过 2000 时再考虑分片。2.2 5.2.1 的依赖坐标与版本对齐SpringBoot 和 ShardingSphere 的版本对应关系是第一个容易翻车的地方。5.2.1 官方推荐搭配 SpringBoot 2.6.x 或 2.7.x如果你用的是 SpringBoot 3.x需要额外处理 Jakarta EE 命名空间的问题因为 5.2.1 内部部分依赖仍然基于 javax。下面是我在 SpringBoot 2.7.18 上验证过的依赖配置。!-- pom.xml 关键依赖 -- properties shardingsphere.version5.2.1/shardingsphere.version /properties dependencies !-- ShardingSphere JDBC starter5.2.1 版本 -- dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core-spring-boot-starter/artifactId version${shardingsphere.version}/version /dependency !-- 如果使用 MyBatis-Plus需要排除其自带的分页插件冲突 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- 数据库驱动以 MySQL 为例 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies这里有几个参数需要说明shardingsphere-jdbc-core-spring-boot-starter会自动装配DataSource所以你的项目里不能再手动定义同名的DataSourceBean否则会出现循环依赖或覆盖问题。MyBatis-Plus 的版本选择上3.5.x 对 ShardingSphere 的兼容性比 3.4.x 好因为 3.5.x 的分页插件在获取数据库类型时不会强制走原生 Connection。MySQL 驱动建议用 8.0.33 以上低版本在分片场景下偶发Communications link failure。2.3 分片键选择的三个硬约束分片键选错后面所有优化都是徒劳。我在实际项目里总结出三条约束第一分片键必须出现在绝大多数查询的 WHERE 条件里否则会触发全路由性能比不分片还差第二分片键的基数要足够大比如用性别做分片键就只有两个分片毫无意义第三分片键一旦确定后期迁移成本极高所以要在业务初期就规划好。以订单表为例常见的分片键是user_id或order_id。如果按user_id分片那么「查询某个用户的所有订单」能精准路由但「按订单号查询」就会全路由。如果按order_id分片则相反。我一般会建议用user_id做分片键因为用户维度的查询在业务中占比更高订单号查询可以通过基因法或冗余字段解决。3. 在 SpringBoot 里跑通第一个分片查询配置与代码3.1 application.yml 的完整分片配置5.2.1 的 YAML 配置结构和 4.x 差异很大核心是spring.shardingsphere.rules.sharding下面挂tables和sharding-algorithms。下面是一个按user_id取模分 4 张表的配置数据源用两个库做示例。spring: shardingsphere: # 数据源定义ds0 和 ds1 是两个物理库 datasource: names: ds0,ds1 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db0?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db1?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root # 分片规则 rules: sharding: # 默认分库策略按 user_id 取模分到 ds0 或 ds1 default-database-strategy: standard: sharding-column: user_id sharding-algorithm-name: db-inline # 默认分表策略按 user_id 取模分到 t_order_0 到 t_order_3 default-table-strategy: standard: sharding-column: user_id sharding-algorithm-name: table-inline # 分片算法定义 sharding-algorithms: db-inline: type: INLINE props: algorithm-expression: ds$-{user_id % 2} table-inline: type: INLINE props: algorithm-expression: t_order_$-{user_id % 4} # 逻辑表声明 tables: t_order: actual-data-nodes: ds$-{0..1}.t_order_$-{0..3} # 主键生成策略5.2.1 支持雪花算法 key-generate-strategy: column: order_id key-generator-name: snowflake # 分布式主键生成器 key-generators: snowflake: type: SNOWFLAKE props: worker-id: 1 # 开启 SQL 日志排查路由问题时必开 props: sql-show: true这段配置里有几个参数需要重点解释。actual-data-nodes的表达式ds$-{0..1}.t_order_$-{0..3}表示实际存在 2 个库各 4 张表共 8 个物理节点。default-database-strategy和default-table-strategy分别控制分库和分表的路由逻辑如果某张表不需要分库可以在tables下面单独覆盖。sql-show: true会打印路由后的真实 SQL调试阶段一定要开上线后建议关掉以免日志膨胀。3.2 用 MyBatis-Plus 写一个精准路由的查询配置完成后应用层的代码几乎不需要改动你仍然按逻辑表t_order来写 Mapper。下面是一个按user_id查询订单的示例。// OrderMapper.java Mapper public interface OrderMapper extends BaseMapperOrder { // 按 user_id 查询会精准路由到对应分片 Select(SELECT * FROM t_order WHERE user_id #{userId}) ListOrder selectByUserId(Param(userId) Long userId); // 按 order_id 查询会全路由到所有分片 Select(SELECT * FROM t_order WHERE order_id #{orderId}) Order selectByOrderId(Param(orderId) Long orderId); }// 测试类片段 SpringBootTest class OrderMapperTest { Autowired private OrderMapper orderMapper; Test void testSelectByUserId() { // user_id 10011001 % 2 1路由到 ds11001 % 4 1路由到 t_order_1 ListOrder orders orderMapper.selectByUserId(1001L); System.out.println(精准路由结果条数 orders.size()); } Test void testSelectByOrderId() { // order_id 不是分片键会广播到所有 8 个节点 Order order orderMapper.selectByOrderId(123456789L); System.out.println(全路由结果 order); } }逻辑说明selectByUserId的 WHERE 条件里包含分片键user_idShardingSphere 解析 SQL 后计算出user_id % 2和user_id % 4只向对应的物理节点发送查询这是精准路由。selectByOrderId的 WHERE 条件里没有分片键ShardingSphere 无法计算路由只能向所有节点广播查询然后合并结果这是全路由。参数说明Param(userId)是 MyBatis 的命名参数ShardingSphere 在解析时能识别到user_id ?的绑定值所以能正确路由。3.3 验证分片是否生效的三种手段配置写完不代表分片就生效了我一般用三种方式交叉验证。第一种是看sql-show打印的日志精准路由时只会打印一条真实 SQL全路由时会打印多条。第二种是直接连到物理库检查数据是否按预期分散到了不同表。第三种是在代码里注入ShardingSphereDataSource通过getContextManager()获取分片上下文但 5.2.1 的 API 有变化下面是一个可用的检查方式。// 检查实际数据节点分布 Autowired private DataSource dataSource; Test void checkActualNodes() throws SQLException { try (Connection conn dataSource.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT COUNT(*) FROM t_order)) { if (rs.next()) { System.out.println(逻辑表总行数 rs.getInt(1)); } } // 再分别连 ds0 和 ds1 查物理表行数确认数据确实分散 }这里要注意dataSource注入的是 ShardingSphere 包装后的逻辑数据源直接执行SELECT COUNT(*)会走全路由汇总所有分片的结果。如果你想看单个物理表的数据需要绕过 ShardingSphere用原生 JDBC 连到具体库。4. 避坑与排查5.2.1 整合 SpringBoot 的五个血泪经验4.1 启动报错「Cannot find sharding algorithm」现象项目启动时抛出Cannot find sharding algorithm或Sharding algorithm not found配置里明明写了sharding-algorithms。原因5.2.1 的算法名称是大小写敏感的而且type字段的值必须是全大写比如INLINE、MOD、HASH_MOD。如果你写成inline或Inline启动时不会报错但运行到路由阶段才会抛异常。解决统一用全大写并且确保sharding-algorithms下面的名称和default-database-strategy里引用的名称完全一致。4.2 分页查询返回重复数据现象用 MyBatis-Plus 的分页插件查t_order第一页和第二页出现重复记录。原因ShardingSphere 在分片场景下分页查询会先在各分片执行LIMIT再在内存中合并排序如果排序字段不是分片键各分片返回的顺序可能不一致导致合并后出现重复或遗漏。解决分页查询的排序字段尽量带上分片键比如ORDER BY user_id, create_time这样各分片内部有序合并结果才稳定。如果业务不允许可以考虑用 ShardingSphere 的LIMIT归并优化但 5.2.1 对复杂排序的支持有限。4.3 分布式主键生成器不生效现象插入数据时order_id为 null或者报主键冲突。原因key-generate-strategy配置在tables下面但很多人漏了key-generators的定义或者column写成了数据库字段名而不是实体类属性名。解决确认key-generators里定义了snowflake并且tables.t_order.key-generate-strategy.column的值和实体类里TableId标注的字段名一致。另外雪花算法的worker-id在集群环境下必须唯一否则会生成重复 ID。4.4 跨库 JOIN 查询报错现象执行包含 JOIN 的 SQL 时抛出Unsupported SQL或路由异常。原因ShardingSphere 5.2.1 对跨库 JOIN 的支持有限如果两张表的分片键不同或者 JOIN 条件里没有分片键ShardingSphere 无法确定路由路径。解决尽量避免跨库 JOIN把关联查询拆成两次单表查询在应用层组装。如果必须 JOIN确保两张表的分片键一致并且 JOIN 条件里包含分片键。4.5 SpringBoot 3.x 下的类加载冲突现象在 SpringBoot 3.x 项目里引入 5.2.1启动时报NoClassDefFoundError: javax/servlet/Filter或类似错误。原因SpringBoot 3.x 迁移到了 Jakarta EE包名从javax.*变成了jakarta.*而 ShardingSphere 5.2.1 的部分依赖仍然引用javax。解决要么降级到 SpringBoot 2.7.x要么手动排除冲突依赖并引入对应的 Jakarta 版本。我一般建议在 5.2.1 阶段还是用 SpringBoot 2.7.x等 ShardingSphere 5.3.x 以上再考虑 3.x。5. 进阶技巧用 Hint 强制路由与分片键基因法5.1 Hint 强制路由的使用场景有些查询确实无法带上分片键比如运营后台按订单号查详情。这时候可以用 ShardingSphere 的 Hint 机制在代码里强制指定路由到某个库或某张表。5.2.1 的 Hint 用法如下。// 强制路由到 ds0 的 t_order_0 HintManager hintManager HintManager.getInstance(); hintManager.addDatabaseShardingValue(t_order, 0L); hintManager.addTableShardingValue(t_order, 0L); try { // 这里的查询会忽略 SQL 里的分片键直接走 Hint 指定的节点 Order order orderMapper.selectByOrderId(123456789L); } finally { // 必须关闭否则会污染后续查询 hintManager.close(); }逻辑说明addDatabaseShardingValue和addTableShardingValue分别指定库和表的分片值ShardingSphere 会优先使用 Hint 值而不是 SQL 解析结果。参数说明第一个参数是逻辑表名第二个参数是分片值。注意HintManager是线程绑定的用完后必须close()否则同一个线程后续的查询都会走这个路由。5.2 分片键基因法解决订单号查询Hint 虽然能解决问题但需要改代码而且运营后台的查询条件千变万化不可能每个都加 Hint。更优雅的方案是基因法把user_id的后几位嵌入到order_id里这样按order_id查询时也能反推出user_id的分片位置。具体做法是生成订单号时取user_id % 4的值作为订单号的最后两位查询时先解析出这两位再拼上user_id去路由。// 生成订单号时嵌入分片基因 public Long generateOrderId(Long userId) { long snowflakeId snowflake.nextId(); // 取 user_id 的低 2 位作为基因拼到订单号末尾 long gene userId % 4; return snowflakeId * 10 gene; } // 查询时从订单号反推分片键 public Long extractUserId(Long orderId) { // 实际项目中需要结合用户表或其他方式还原完整 user_id // 这里只演示基因位的提取 long gene orderId % 10; return gene; // 用于路由到对应分片 }这个方案的代价是订单号变长而且需要保证基因位不会和雪花算法的序列号冲突。我一般会把基因位放在订单号的固定位置比如倒数第三位然后在查询时用位运算提取。实际落地时还要考虑历史数据的兼容如果订单表已经有存量数据基因法只能对新数据生效。5.3 上线前的验证清单在把分片配置推到生产之前我习惯跑一遍下面的检查清单。第一确认所有分片表的建表语句已经在每个物理库执行表名和actual-data-nodes完全匹配。第二用EXPLAIN检查核心查询是否走了精准路由全路由的查询要评估是否可接受。第三压测写入性能确认分布式主键生成器没有成为瓶颈。第四检查事务边界ShardingSphere 对跨库事务的支持需要额外配置XA或BASE5.2.1 默认是本地事务跨库写入会报错。第五准备好回滚方案分片配置一旦上线回退到单库需要数据迁移。这些检查里跨库事务是最容易被忽略的。如果你的业务有跨库写入必须在配置里开启transaction规则并且引入shardingsphere-transaction-xa-core依赖。但 XA 的性能损耗明显我一般建议尽量避免跨库事务把相关表用同一个分片键分到同一个库。希望帮到你。本文还有配套的精品资源点击获取
返回列表