ShardingSphere 5.5.1 集成 dynamic-datasource:实现多数据源下的按月分表与自动建表实战
1. 为什么需要多数据源按月分表在业务系统发展到一定规模后单表数据量膨胀会带来明显的性能问题。我去年接手过一个风控系统核心的risk_record表每月新增数据超过200万条查询响应时间从最初的200ms逐渐恶化到2秒以上。这时候就需要考虑分表方案了。按月分表是最常见的分片策略之一相比按ID哈希分片有三大优势时间维度查询友好风控场景下90%的查询都带有时间范围条件数据归档方便直接按月份进行历史数据迁移业务可读性强risk_record_202401比risk_record_03更直观但传统分表方案在多数据源环境下会遇到两个头疼问题不同业务线的数据需要隔离到独立数据库实例新增月份表需要手动维护容易遗漏这就是为什么我们要结合ShardingSphere和dynamic-datasource——前者解决分片路由问题后者管理多数据源连接。下面我会用真实项目配置为例展示如何实现这个黄金组合。2. 环境搭建与依赖配置2.1 组件版本选择先看我的环境组合这是踩过几次坑后验证稳定的版本properties spring-boot.version2.7.18/spring-boot.version shardingsphere.version5.5.1/shardingsphere.version dynamic-datasource.version3.6.1/dynamic-datasource.version /properties !-- ShardingSphere核心依赖 -- dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc/artifactId version${shardingsphere.version}/version /dependency !-- 多数据源支持 -- dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version${dynamic-datasource.version}/version /dependency这里有个关键点ShardingSphere 5.x开始官方不再维护spring-boot-starter必须改用shardingsphere-jdbc原生集成方式。我在升级时就因为这个配置浪费了半天时间。2.2 数据源双层配置多数据源环境下需要特别注意配置顺序spring: datasource: dynamic: primary: nemesismaster # 默认数据源 datasource: nemesismaster: # 真实数据源1 url: jdbc:mysql://db1:3306/nemesis username: admin password: 123456 statistics: # 真实数据源2 url: jdbc:mysql://db2:3306/stat username: admin password: 123456 shardingSphere: # ShardingSphere虚拟数据源 url: jdbc:shardingsphere:classpath:sharding.yml driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver这种结构实现了逻辑分层dynamic-datasource管理物理数据源ShardingSphere在顶层做分片路由业务代码只需注入DS(shardingSphere)3. 分片规则配置实战3.1 分片算法核心逻辑按月分表的关键在于TimeShardingAlgorithm的实现我提炼了四个核心方法public String doSharding(CollectionString availableTargetNames, PreciseShardingValueTimestamp preciseShardingValue) { // 1. 解析时间戳到月份 LocalDateTime dateTime preciseShardingValue.getValue().toLocalDateTime(); String monthSuffix dateTime.format(DateTimeFormatter.ofPattern(yyyyMM)); // 2. 构造真实表名如risk_record_202401 String actualTable preciseShardingValue.getLogicTableName() _ monthSuffix; // 3. 检查表是否存在不存在则自动创建 if (!availableTargetNames.contains(actualTable)) { createTableIfAbsent(preciseShardingValue.getLogicTableName(), actualTable); } return actualTable; }这里有个性能优化点首次查询时会触发建表操作我通过synchronized双重检查机制避免并发建表冲突。3.2 YAML规则配置详解对应的sharding.yml配置如下rules: - !SHARDING tables: risk_record: actualDataNodes: nemesismaster.risk_record_$-{202401..202412} tableStrategy: standard: shardingColumn: create_time shardingAlgorithmName: month_sharder shardingAlgorithms: month_sharder: type: CLASS_BASED props: strategy: standard algorithmClassName: com.example.sharding.TimeShardingAlgorithm几个容易出错的配置项actualDataNodes中的占位符只是提示作用实际分片取决于算法算法类型必须用CLASS_BASED配合自定义实现分片列create_time需要与表结构严格一致4. 自动建表机制实现4.1 动态表结构复制自动建表的核心是复制基础表结构我封装了通用工具方法public static boolean createShardingTable(String logicTable, String actualTable) { try (Connection conn dataSource.getConnection()) { // 使用LIKE语法复制表结构 String sql String.format(CREATE TABLE IF NOT EXISTS %s LIKE %s, actualTable, logicTable); conn.createStatement().execute(sql); return true; } catch (SQLException e) { logger.error(建表失败: {} - {}, logicTable, actualTable, e); return false; } }这里有个细节基础表需要提前在第一个数据源中创建好分表会以其为模板。4.2 分片元数据管理为了提升性能我增加了本地缓存机制private static final CacheString, SetString tableCache Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) .build(); public SetString refreshTables(String logicTable) { SetString tables queryAllTables(logicTable); tableCache.put(logicTable, tables); return tables; }缓存更新策略首次查询全量加载自动建表后局部更新定时任务每小时刷新5. 生产环境注意事项5.1 跨数据源事务处理在多数据源分表场景下事务需要特殊处理。我的解决方案DS(shardingSphere) Transactional(rollbackFor Exception.class) public void processBusiness() { // 方法内所有操作会在同一逻辑事务中 // 实际可能跨多个物理数据源 }需要配合Atomikos等分布式事务管理器使用注意事务超时时间不宜过长避免大事务影响性能测试阶段务必验证回滚逻辑5.2 监控与调优建议推荐配置以下监控指标分片查询命中率自动建表成功率跨库查询耗时在10万级QPS的场景下我总结的调优经验分片算法避免复杂计算合理设置连接池参数热点数据考虑本地缓存这套方案在我们生产环境稳定运行了6个月最高支撑了日增3000万记录的业务量。初期配置确实有些复杂但一旦跑通后后续扩展新的分表业务就非常轻松了。