1. SpringBoot整合ShardingSphere-JDBC 4.0.0按月分表实战
最近在做一个订单管理系统时遇到了单表数据量过大的问题,经过技术选型最终决定采用ShardingSphere-JDBC 4.0.0实现按月分表。这里分享下完整的整合过程和踩坑经验。
按月分表是解决单表数据膨胀的经典方案,特别适合订单、日志等随时间线性增长的业务场景。ShardingSphere作为Apache顶级项目,其JDBC模块可以无缝集成到SpringBoot中,通过配置即可实现分库分表,对业务代码零侵入。
2. 核心设计与技术选型
2.1 为什么选择按月分表
我们的订单表每月新增约300万条数据,单表超过2000万后查询性能明显下降。经过测试发现:
- 按ID哈希分片会导致范围查询需要扫描所有分片
- 按年分片粒度太粗,单个分片仍然过大
- 按月分片既能控制单表数据量(300万/月),又保持了时间维度的查询效率
2.2 ShardingSphere-JDBC版本选择
选择4.0.0版本主要考虑:
- 兼容SpringBoot 2.x生态
- 支持更灵活的分片策略配置
- 提供完善的分布式事务支持
- 社区活跃,文档齐全
注意:4.x版本配置与5.x有较大差异,网上很多教程是基于5.x的,需要注意区分
3. 完整整合步骤
3.1 环境准备
<!-- pom.xml依赖 --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.5</version> </dependency>3.2 分表配置详解
# application-sharding.yml spring: shardingsphere: datasource: names: ds0 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/order_db username: root password: 123456 sharding: tables: t_order: actual-data-nodes: ds0.t_order_$->{2020..2030}0$->{1..9},ds0.t_order_$->{2020..2030}1$->{0..2} table-strategy: standard: precise-algorithm-class-name: com.example.config.MonthShardingAlgorithm range-algorithm-class-name: com.example.config.MonthRangeShardingAlgorithm sharding-column: create_time3.3 分片算法实现
public class MonthShardingAlgorithm implements PreciseShardingAlgorithm<Date> { @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) { // 格式: t_order_yyyyMM SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM"); return "t_order_" + sdf.format(shardingValue.getValue()); } } public class MonthRangeShardingAlgorithm implements RangeShardingAlgorithm<Date> { @Override public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> shardingValue) { // 处理BETWEEN AND查询 Set<String> result = new LinkedHashSet<>(); DateRange range = new DateRange(shardingValue.getValueRange()); for (Date date = range.lowerEndpoint(); date.before(range.upperEndpoint()); date = DateUtils.addMonths(date, 1)) { result.add("t_order_" + new SimpleDateFormat("yyyyMM").format(date)); } return result; } }4. 关键问题与解决方案
4.1 分布式ID生成
分表后自增ID会导致重复,我们采用Snowflake算法:
@Bean public KeyGenerator keyGenerator() { return new SnowflakeKeyGenerator(); }4.2 跨月查询优化
对于跨多个月份的查询,我们做了以下优化:
- 在查询条件中尽量带上时间范围
- 对分页查询使用归并排序
- 建立月份维度的联合索引
4.3 历史数据迁移
已有数据需要按月份迁移到对应分表:
-- 按月导出数据 SELECT * INTO OUTFILE '/tmp/order_202001.csv' FROM t_order WHERE create_time BETWEEN '2020-01-01' AND '2020-01-31'; -- 导入到分表 LOAD DATA INFILE '/tmp/order_202001.csv' INTO TABLE t_order_202001;5. 性能测试对比
测试环境:8核16G,MySQL 5.7
| 场景 | 单表(2000万) | 按月分表(300万/表) |
|---|---|---|
| 单条查询 | 120ms | 15ms |
| 月统计报表 | 2.3s | 0.4s |
| 跨3个月查询 | 3.1s | 0.7s |
| 写入性能 | 800TPS | 1500TPS |
6. 生产环境注意事项
分表键选择:create_time字段必须包含在WHERE条件中,否则会全表路由
索引设计:每个分表需要单独建立索引,不能依赖全局索引
事务处理:跨分片事务需要使用XA或SAGA模式
监控配置:
spring: shardingsphere: metrics: enabled: true name: sharding_stats- SQL限制:避免使用子查询、UNION等复杂操作
经过三个月的生产验证,这套方案成功将订单查询性能提升了5-8倍,写入吞吐量提升近2倍。最大的收获是:分表不是银弹,必须配合合理的查询方式和索引设计才能发挥最大效益。