Sharding-jdbc 有20年数据,每年分一张表,查询指定日期近两年数据,跨年查询如何处理
在 Sharding-JDBC 中处理每年一张表且查询指定日期近两年跨年数据的场景,核心是通过自定义范围分片算法(RangeShardingAlgorithm)精准定位目标年份的表,并自动合并结果。以下是具体实现方案、关键细节和验证步骤:
一、场景再明确
- 表结构:订单表按年分表,命名为
t_order_YYYY(如t_order_2022、t_order_2023、t_order_2024…t_order_2042,共20张表)。 - 分片键:订单创建时间
create_time(DATETIME类型)。 - 查询需求:给定指定日期(如
2024-06-01),查询该日期前推两年的数据(即2022-06-01 ~ 2024-06-01),涉及2022、2023、2024三年表。
二、核心解决方案:RangeShardingAlgorithm 处理时间范围
Sharding-JDBC 的范围分片算法(RangeShardingAlgorithm)用于处理时间区间查询,其作用是:
接收 SQL 中的 create_time 范围条件,计算对应的年份区间,返回需要查询的分表集合(如 t_order_2022、t_order_2023、t_order_2024)。
三、具体实现步骤
1. 自定义 RangeShardingAlgorithm
实现算法逻辑:从分片值中提取时间范围,计算起始/结束年份,筛选对应的表名。
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;import java.util.*;
import java.util.regex.Pattern;/*** 按年分表的 RangeShardingAlgorithm(处理指定日期近两年查询)* 示例:查询 create_time ∈ [start, end],返回对应年份的表*/
public class OrderYearRangeShardingAlgorithm implements RangeShardingAlgorithm<Date> {// 表名前缀(如 "t_order_")private static final String TABLE_PREFIX = "t_order_";// 年份正则(从表名中提取年份)private static final Pattern YEAR_PATTERN = Pattern.compile(TABLE_PREFIX + "(\\d{4})");@Overridepublic Collection<String> doSharding(Collection<String> availableTargetNames, // 所有可选分表(如 t_order_2022~t_order_2042)RangeShardingValue<Date> shardingValue // 分片值(包含 create_time 的范围)) {// 1. 提取查询的时间范围(start/end)Date startTime = shardingValue.getValueRange().lowerEndpoint();Date endTime = shardingValue.getValueRange().upperEndpoint();// 2. 计算起始年份和结束年份int startYear = getYearFromDateTime(startTime);int endYear = getYearFromDateTime(endTime);// 3. 筛选符合条件的分表(年份在 [startYear, endYear] 之间)Set<String> result = new HashSet<>();for (String tableName : availableTargetNames) {// 从表名中提取年份(如 t_order_2023 → 2023)Integer tableYear = extractYearFromTableName(tableName);if (tableYear != null && tableYear >= startYear && tableYear <= endYear) {result.add(tableName);}}return result;}/*** 从 Date 中提取年份*/private int getYearFromDateTime(Date date) {Calendar cal = Calendar.getInstance();cal.setTime(date);return cal.get(Calendar.YEAR);}/*** 从表名中提取年份(如 t_order_2023 → 2023)*/private Integer extractYearFromTableName(String tableName) {java.util.regex.Matcher matcher = YEAR_PATTERN.matcher(tableName);if (matcher.matches()) {return Integer.parseInt(matcher.group(1));}return null; // 非分表,忽略}
}
2. 配置 Sharding-JDBC(Spring Boot YAML)
绑定表名规则和自定义算法:
spring:shardingsphere:datasource:names: ds # 单数据源(所有分表在同一库,也可分散在不同库)ds:url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTCusername: rootpassword: 123456rules:sharding:tables:t_order:# 实际分表节点:所有年份的表(2022~2042)actual-data-nodes: ds.t_order_${2022..2042}# 分库策略(无分库,所有表在同一库)database-strategy:none:# 分表策略:按 create_time 的年份分表table-strategy:standard:sharding-column: create_time# 绑定自定义的 RangeShardingAlgorithmsharding-algorithm-name: order_year_range# 自定义分片算法sharding-algorithms:order_year_range:type: CLASS_BASEDprops:strategy: STANDARDalgorithm-class-name: com.example.config.OrderYearRangeShardingAlgorithm # 自定义算法的全限定类名
四、查询验证:SQL 路由与结果合并
1. 示例 SQL(查询指定日期近两年数据)
假设指定日期为 2024-06-01,查询 create_time 在 2022-06-01 ~ 2024-06-01 之间的订单:
SELECT * FROM t_order
WHERE create_time BETWEEN '2022-06-01 00:00:00' AND '2024-06-01 23:59:59';
2. 路由逻辑拆解
- 解析分片键:Sharding-JDBC 提取 SQL 中的
create_time范围条件(2022-06-01到2024-06-01)。 - 调用 RangeShardingAlgorithm:
- 计算起始年份
2022,结束年份2024。 - 从
availableTargetNames(所有分表)中筛选出t_order_2022、t_order_2023、t_order_2024。
- 计算起始年份
- 执行分片查询:向这三张表分别发送 SQL,查询对应年份的数据。
- 合并结果:Sharding-JDBC 自动将三张表的查询结果合并,返回完整的跨年数据集。
五、关键注意事项
1. SQL 必须包含分片键条件
跨年查询必须包含 create_time 的范围条件,否则 Sharding-JDBC 无法路由,会触发全表扫描(查询所有20张表)。
错误示例:SELECT * FROM t_order(无分片键,全表扫描)。
正确示例:SELECT * FROM t_order WHERE create_time >= '2022-06-01'(包含分片键)。
2. 指定日期的处理逻辑
若指定日期是 D,近两年的时间范围通常是 D - 2年 到 D(如 2024-06-01 → 2022-06-01 ~ 2024-06-01)。
算法中需准确计算起始年份和结束年份,避免遗漏或多查表。
3. 性能优化
- 索引优化:在
create_time字段上建立索引,加速范围查询(尤其是跨多张表时)。 - 分表数量控制:若20年数据导致分表过多(如20张),可考虑按月分表(更细粒度,但需调整算法)。
- 归档历史数据:将旧数据迁移至归档库,减少当前库的分表数量。
4. 结果排序与分页
- 全局排序:若 SQL 包含
ORDER BY create_time,Sharding-JDBC 会合并分片结果后二次排序,无需额外处理。 - 分页:
LIMIT/OFFSET会由 Sharding-JDBC 合并分片结果后计算,确保正确性。
六、验证路由正确性
开启 Sharding-JDBC 的 SQL 日志,查看实际路由的表:
spring:shardingsphere:props:sql-show: true # 打印 SQL 路由日志
日志示例:
DEBUG [main] o.a.s.r.r.RouteUnit - Logic SQL: SELECT * FROM t_order WHERE create_time BETWEEN ? AND ?
DEBUG [main] o.a.s.r.r.RouteUnit - Actual SQL: ds ::: SELECT * FROM t_order_2022 WHERE create_time BETWEEN ? AND ?
DEBUG [main] o.a.s.r.r.RouteUnit - Actual SQL: ds ::: SELECT * FROM t_order_2023 WHERE create_time BETWEEN ? AND ?
DEBUG [main] o.a.s.r.r.RouteUnit - Actual SQL: ds ::: SELECT * FROM t_order_2024 WHERE create_time BETWEEN ? AND ?
七、总结
Sharding-JDBC 处理每年一张表的跨年查询的核心是:
- 自定义 RangeShardingAlgorithm:处理时间范围,返回对应年份的表。
- SQL 包含分片键:确保路由到正确的表。
- 自动合并结果:Sharding-JDBC 处理跨表结果的合并与排序。
通过以上配置,可高效支持“指定日期近两年”的跨年查询,同时兼容长期数据扩展(新增年份表只需更新 actual-data-nodes 或算法逻辑)。
