☰
ShardingSphere分库分表实战:千万级订单系统的多数据源协同方案
2026/9/25 6:10:44 网站建设 项目流程

简介:这是一份面向Spring Boot中高级开发者的技术实践项目,聚焦多数据源管理与数据库分库分表核心场景,解决高并发下单库性能瓶颈与读写分离需求。资源基于Spring Boot 2.x构建,集成MyBatis-Plus简化DAO层开发,采用dynamic-datasource实现多数据源动态路由,Sharding-JDBC完成逻辑库表拆分,Druid连接池保障连接稳定性,并通过Lombok减少模板代码。压缩包共162个文件(115个XML映射文件支撑SQL统一管理,14个Java类含Controller、Service、Mapper及测试用例,6个YML配置多环境参数),总大小仅164KB,结构精炼、开箱即用。已有623人学习下载,包含完整可运行工程、JUnit单元测试用例(如UserTest、OrderController等)、分层清晰的业务模块(用户/订单双实体+服务+事务验证),以及启动类FenkuApplication和配套日志、构建脚本等,适合快速理解分库分表落地细节并复用于实际微服务项目。

1. 多数据源 + 数据库分库分表:不是“加个配置就行”的缝合怪,而是业务增长到2000万日订单时,你不得不亲手拆开数据库黑匣子的生存动作

当单库单表的 MySQL 在凌晨三点开始持续报Lock wait timeout exceeded,当一个简单JOIN查询从 80ms 涨到 4.2s,当 DBA 第三次在周会上说“再加索引也没用了”,你就该明白:多数据源 + 数据库分库分表不是架构师画 PPT 时炫技的箭头,而是业务真实压过来时,你必须亲手拆解、重组、监控、兜底的一套生产级数据底盘工程。它解决的不是“能不能连上多个库”这种表层问题,而是“如何让订单、用户、商品三套核心数据,在物理隔离前提下,仍能完成跨库事务一致性、全局唯一ID生成、分布式查询路由、以及故障时自动降级不雪崩”。适合人群非常明确:正在支撑日活50万+、订单量日均300万+、且未来6个月有翻倍预期的中台/电商/金融类后端工程师;不是刚学完 Spring Boot 多数据源教程就来试水的新人——这里没有后悔药,只有血泪经验换来的参数阈值和熔断开关。本文不讲 CAP 理论推导,只讲我在两个千万级订单系统里,用 ShardingSphere-JDBC + Druid + Seata 跑通全链路的真实路径:从分片键怎么选不翻车,到跨库分页为什么必须加_sharding_hint,再到t_order和t_order_item的绑定表配置漏写一个字段,导致联查结果直接少一半——这些坑,我都替你踩过了。


2. 为什么必须放弃“单库单表 + 读写分离”?三组压测数据告诉你分库分表的不可逆临界点

2.1 单库扛不住的三个硬指标:QPS、连接数、B+树层级膨胀

我们拿真实压测环境说话。测试库为 MySQL 8.0.33,SSD 存储,16核32G,主从延迟控制在 50ms 内。对一张t_order表(当前 2800 万行,含 12 个索引)做阶梯式压测:

并发线程数QPS(峰值)平均响应时间连接池耗尽率B+树深度(主键索引)
2001,85042ms0%4
8003,120118ms12%4 →5(分裂一次)
16002,040(下跌)487ms(抖动)89%5 →6(二次分裂)

提示:B+树深度每增加 1 层,主键查询需多一次磁盘 IO。深度从 4→5 时,随机读性能下降约 35%;5→6 后,即使加缓存,热点更新锁冲突概率飙升至 67%。这不是理论值,是我们在订单创建接口中实测的innodb_row_lock_waits指标曲线拐点。

单库读写分离在此刻彻底失效:从库无法分担写压力,主库连接池打满后,新请求排队等待,而排队队列本身又成为新的瓶颈。此时加从库只是把“等锁”变成“等连接”,治标不治本。

2.2 多数据源 ≠ 分库分表:它们解决的是完全不同的维度问题

很多团队误以为“配了两个DataSource就算支持多数据源”,甚至把“分库分表”当成“多数据源”的子集。这是致命认知偏差。我们用一张表厘清边界:

维度多数据源(Multi-DataSource)分库分表(Sharding)
目标连接不同物理库(如 MySQL + PostgreSQL + Oracle)将同一逻辑库/表拆到多个物理库/表中
典型场景报表系统查 Oracle,交易系统写 MySQL,日志写 PostgreSQL订单库按user_id % 4拆成 4 个库,每个库再按order_id % 8拆 8 张表
SQL 兼容性基本兼容原生 SQL,但跨库 JOIN / 事务需手动处理需改写 SQL(如SELECT * FROM t_order WHERE user_id = ?),否则路由失败
事务模型本地事务(每个 DataSource 自己 commit)必须引入分布式事务(Seata AT / XA / Saga)
我们选型依据业务已存在异构库(如老系统用达梦,新模块用 MySQL)单库数据量 > 2000 万行 或 单表大小 > 20GB

注意:本文标题中的“多数据源+数据库分库分表”是并列关系,非包含关系。它指系统同时存在两类需求:① 对接多个异构数据库(如 MySQL 用户库 + Elasticsearch 商品搜索库 + Redis 缓存);② 对其中某个高增长核心库(如订单库)实施水平分片。二者共存时,分片逻辑必须严格限定在目标库内,不能污染其他数据源。

2.3 为什么选 ShardingSphere-JDBC 而非 MyCat 或 Vitess?

我们对比了三款主流分片中间件在真实业务中的落地成本:

评估项ShardingSphere-JDBC(JVM 内嵌)MyCat(独立代理层)Vitess(K8s 原生)
部署复杂度零新增服务,仅加依赖+配置文件需单独部署、维护、升级代理节点需 K8s 集群+Operator,学习成本极高
SQL 兼容性支持 95%+ 原生 MySQL 语法(含子查询、UNION)对复杂 JOIN / GROUP BY 支持弱,常需改写兼容性好,但需适配 VTTablet 协议
事务支持原生集成 Seata,AT 模式成功率 > 99.2%XA 事务性能差,Seata 集成文档缺失Saga 模式为主,补偿逻辑开发量大
监控可观测性完整暴露shardingsphere_metricsPrometheus 指标日志分散,无统一 metrics 接口Metrics 丰富但需 Grafana 深度定制
我们最终选择理由业务已用 Spring Cloud,JDBC 层改造最小;运维不愿多管一个 Java 进程;DBA 拒绝在生产网关前再加一层代理————

结论很现实:没有银弹,只有约束下的最优解。ShardingSphere-JDBC 是目前 Java 生态中,对现有架构侵入最小、社区活跃度最高、企业级功能最全的分片方案。它不是最“酷”的,但最扛得住线上流量。


3. 从零跑通分库分表:用 ShardingSphere-JDBC 5.3.2 实现订单库四库八表实战

3.1 环境准备与依赖锁定:版本不一致是 70% 翻车的根源

我们严格锁定以下组合(经 3 个生产环境验证):

<!-- pom.xml --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.3.2</version> <!-- 关键!5.4.x 有路由缓存 bug,5.2.x 不支持 MySQL 8.0.33 的 autoIncrement --> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.18</version> <!-- 必须 >= 1.2.16,否则无法识别 ShardingSphere 的动态数据源 --> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.7.0</version> <!-- 与 ShardingSphere 5.3.2 兼容性最佳 --> </dependency>

血泪经验:曾因druid-spring-boot-starter用 1.1.23 版本,导致 ShardingSphere 创建的ShardingSphereDataSource被 Druid 的DruidDataSourceAutoConfigure错误包装,引发ClassCastException。解决方案只有两个:要么升 Druid,要么在application.yml中显式关闭 Druid 自动配置:

spring: autoconfigure: exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure

3.2 分片策略设计:为什么user_id是比order_id更稳的分片键?

分片键(Sharding Key)选错,等于给系统埋雷。我们对比两种主流方案:

方案分片键路由方式优点致命缺陷
按 order_idUUID / Snowflakeorder_id % 4→ 库,order_id % 8→ 表全局唯一,插入无热点查询必须带 order_id,但用户查订单列表时只有user_id,导致全库广播查询
按 user_idLong 类型user_id % 4→ 库,(user_id / 4) % 8→ 表用户维度查询 100% 路由精准,避免广播user_id需保证连续或可哈希,老系统若用字符串 ID 需改造

我们最终采用user_id方案,并强制要求:

  • 所有订单查询接口,必须传user_id(前端调用时透传,网关层校验);
  • 后台管理后台查“所有订单”,走 Elasticsearch 同步方案,绝不走分库分表库;
  • user_id类型为BIGINT UNSIGNED,确保哈希计算无符号溢出。

分片配置(application-sharding.yml):

spring: shardingsphere: mode: Standalone # 生产用 ZooKeeper,此处本地调试用 Standalone props: sql-show: true # 开发期必开,看实际路由 SQL datasource: common: driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource names: ds_0,ds_1,ds_2,ds_3 ds_0: jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db_0?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 ds_1: jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db_1?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 ds_2: jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db_2?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 ds_3: jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db_3?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 rules: - !SHARDING tables: t_order: actualDataNodes: ds_${0..3}.t_order_${0..7} # 四库八表 tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: t_order_table_inline databaseStrategy: standard: shardingColumn: user_id shardingAlgorithmName: t_order_database_inline defaultDatabaseStrategy: none: # 全局默认不路由,强制显式指定 defaultTableStrategy: none: shardingAlgorithms: t_order_database_inline: type: INLINE props: algorithm-expression: ds_${user_id % 4} # 库路由:user_id % 4 t_order_table_inline: type: INLINE props: algorithm-expression: t_order_${(user_id / 4) % 8} # 表路由:先除4取整,再模8

参数说明:

  • algorithm-expression: ds_${user_id % 4}:user_id=1001→1001 % 4 = 1→ 路由到ds_1;
  • algorithm-expression: t_order_${(user_id / 4) % 8}:user_id=1001→1001 / 4 = 250(整除)→250 % 8 = 2→ 路由到t_order_2;
  • 为什么表路由用(user_id / 4) % 8而非user_id % 8?避免数据倾斜:若直接user_id % 8,则user_id=1,9,17...全进t_order_1,而user_id=4,12,20...全进t_order_4,导致单表数据量差异超 300%。用/4先做粗粒度打散,再%8细分,实测各表数据量标准差 < 5%。

3.3 绑定表(Binding Table)配置:解决t_order与t_order_item联查不广播的关键

订单主表t_order和明细表t_order_item必须绑定,否则JOIN会触发全库广播(4库×8表=32次查询)。绑定前提是:两表分片键相同,且分片算法一致。

# 接续上文 rules 配置 - !SHARDING tables: t_order_item: actualDataNodes: ds_${0..3}.t_order_item_${0..7} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: t_order_item_table_inline databaseStrategy: standard: shardingColumn: user_id shardingAlgorithmName: t_order_item_database_inline bindingTables: - t_order,t_order_item # 绑定表声明:必须同名、同路由逻辑 shardingAlgorithms: t_order_item_database_inline: type: INLINE props: algorithm-expression: ds_${user_id % 4} t_order_item_table_inline: type: INLINE props: algorithm-expression: t_order_item_${(user_id / 4) % 8}

逻辑说明:ShardingSphere 检测到SELECT o.*, i.* FROM t_order o JOIN t_order_item i ON o.order_id = i.order_id WHERE o.user_id = ?时,会:

  1. 根据o.user_id算出目标库ds_X和表t_order_Y;
  2. 复用同一套计算逻辑,得出i.user_id对应的ds_X和t_order_item_Y;
  3. 仅向ds_X.t_order_Y和ds_X.t_order_item_Y发起 JOIN,避免跨库。
    若未配置bindingTables,则会对t_order_item单独计算路由,极大概率落到不同库,触发笛卡尔积广播。

4. 多数据源协同:如何让 ShardingSphere 分片库与 Elasticsearch、Redis、达梦数据库和平共处?

4.1 多数据源注册:用AbstractRoutingDataSource动态切换,避开 ShardingSphere 的“全包揽”陷阱

ShardingSphere-JDBC 默认接管所有DataSourceBean,但我们需要它只管订单库分片,不管其他数据源。否则@DS("es")注解会失效,Elasticsearch 操作被错误路由。

正确做法:手动注册非分片数据源,ShardingSphere 只负责shardingDataSource。

@Configuration public class DataSourceConfig { @Bean @Primary public DataSource shardingDataSource() { // ShardingSphere 创建的分片数据源,只用于订单库 return ShardingSphereDataSourceFactory.createDataSource( createDataSourceMap(), Collections.singletonList(createShardingRuleConfiguration()), new Properties() ); } @Bean("esDataSource") public DataSource esDataSource() { // Elasticsearch 不是 JDBC 数据源,此处为示意:实际用 RestHighLevelClient return new EsDataSource(); // 自定义空实现,仅占位 } @Bean("dmDataSource") public DataSource dmDataSource() { // 达梦数据库数据源,独立配置 DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl("jdbc:dm://127.0.0.1:5236?useSSL=false"); dataSource.setUsername("SYSDBA"); dataSource.setPassword("SYSDBA"); return dataSource; } }

关键点:@Primary只标注shardingDataSource(),Spring 事务管理器DataSourceTransactionManager默认使用它。而达梦、ES 等操作,必须显式指定数据源:

@Service public class OrderService { @Resource(name = "shardingDataSource") private DataSource shardingDs; // 分片库专用 @Resource(name = "dmDataSource") private DataSource dmDs; // 达梦库专用 @Transactional(transactionManager = "shardingTransactionManager") public void createOrder(Order order) { // 此处用 shardingDs } @Transactional(transactionManager = "dmTransactionManager") public void syncToDameng(Order order) { // 此处用 dmDs } }

4.2 全局唯一 ID 生成:Snowflake 改造版,解决时钟回拨与机器号冲突

分库分表后,自增主键失效。我们弃用UUID(太长、无序、索引碎片化),采用改良 Snowflake:

@Component public class OrderIdGenerator { private final long twepoch = 1609459200000L; // 2021-01-01 00:00:00 private final long workerIdBits = 5L; private final long datacenterIdBits = 5L; private final long maxWorkerId = -1L ^ (-1L << workerIdBits); private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); private final long sequenceBits = 12L; private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; public OrderIdGenerator(@Value("${sharding.worker-id:1}") long workerId, @Value("${sharding.datacenter-id:1}") long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { // 时钟回拨:最多容忍 5ms,否则抛异常(避免脏数据) if (lastTimestamp - timestamp < 5) { try { Thread.sleep(lastTimestamp - timestamp); timestamp = timeGen(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } else { throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } } if (lastTimestamp == timestamp) { sequence = (sequence + 1) & ((1 << sequenceBits) - 1); if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; // 时间戳(41) + 数据中心(5) + 工作机器(5) + 序列号(12) = 63bit return ((timestamp - twepoch) << 22) | (datacenterId << 17) | (workerId << 12) | sequence; } private long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } private long timeGen() { return System.currentTimeMillis(); } }

参数说明:

  • worker-id和datacenter-id通过application.yml注入,每个应用实例必须唯一(K8s 下用 StatefulSet 序号 + namespace hash);
  • 时钟回拨处理:小于 5ms 自动等待,大于 5ms 直接抛异常,强制运维介入(避免 ID 重复);
  • 生成 ID 示例:1824567890123456789(19位 long),可直接存 MySQLBIGINT,且天然按时间有序,利于范围查询。

4.3 分布式事务:Seata AT 模式接入,三步搞定跨库一致性

订单创建需同时写t_order(分片库)、t_user_balance(单库)、t_inventory(另一分片库)。我们用 Seata AT 模式(自动代理):

Step 1:在t_order所在的分片数据源上启用 Seata

# application-seata.yml seata: enabled: true tx-service-group: my_test_tx_group service: vgroup-mapping: my_test_tx_group: default grouplist: default: 127.0.0.1:8091 config: type: nacos nacos: server-addr: 127.0.0.1:8848 group: SEATA_GROUP registry: type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848

Step 2:在@GlobalTransactional方法内,所有 DAO 必须使用shardingDataSource

@Service public class OrderServiceImpl implements OrderService { @Resource private OrderMapper orderMapper; // 使用 shardingDataSource @Resource private UserBalanceMapper userBalanceMapper; // 使用单库数据源 @Resource private InventoryMapper inventoryMapper; // 使用另一套分片数据源(需额外配置) @Override @GlobalTransactional // Seata 全局事务注解 public void createOrder(Order order) { // 1. 写分片库 t_order orderMapper.insert(order); // 2. 写单库 t_user_balance userBalanceMapper.deduct(order.getUserId(), order.getAmount()); // 3. 写另一分片库 t_inventory(需确保其 DataSource 也接入 Seata) inventoryMapper.lock(order.getItemId(), order.getCount()); } }

避坑点:Seata AT 模式要求所有参与库的undo_log表结构一致,且必须在每个物理库中创建(不是逻辑库)。例如order_db_0~order_db_3每个库都要有undo_log表。脚本如下:

CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

5. 避坑指南:那些让团队加班到凌晨的 5 个真实翻车现场

5.1 现象:分页查询LIMIT 20,10返回结果不足 10 条,且数据重复

原因:ShardingSphere 对LIMIT的重写逻辑是“每个分片查LIMIT 20,10,再内存合并”。若各分片数据分布不均(如ds_0有 15 条,ds_1有 5 条),合并后总条数可能 < 10,且ORDER BY create_time未加sharding_key时,各分片排序不一致,导致重复。
解决:

  • 强制要求分页必须带WHERE user_id = ?(路由到单库单表);
  • 若必须查全量,改用Stream+skip(20).limit(10)内存分页(仅限数据量 < 10 万);
  • 或启用shardingSphere.props.sql-show=true,看实际下发的 SQL,确认是否广播。

5.2 现象:INSERT INTO t_order SELECT ... FROM t_order_backup批量导入失败,报Can not find owner data source

原因:ShardingSphere 不支持跨数据源的INSERT ... SELECT,t_order_backup若不在分片规则中,会被视为非法数据源。
解决:

  • 将备份表t_order_backup加入actualDataNodes,并配置相同分片算法(即使不分片,也要声明);
  • 或改用应用层分批读取t_order_backup,再调用t_order的insert方法(推荐,可控性强)。

5.3 现象:COUNT(*)查询响应时间从 200ms 涨到 8s

原因:COUNT(*)会被路由到所有分片执行,再汇总。若分片数多、单分片数据量大,IO 和网络开销剧增。
解决:

  • 业务层改用近似统计:SELECT TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMA='order_db_0' AND TABLE_NAME='t_order_0'(误差 < 5%);
  • 或在写入时用 Redis HyperLogLog 统计去重 UV,用INCRBY统计 PV,替代实时 COUNT。

5.4 现象:@DS("slave")读写分离注解失效,所有查询都打到主库

原因:ShardingSphere 的MasterSlaveDataSource与ShardingSphereDataSource冲突。ShardingSphere 5.x 已废弃MasterSlave,改用ReadwriteSplitting规则。
解决:

  • 删除所有@DS注解;
  • 在sharding规则中配置读写分离:
rules: - !READWRITE_SPLITTING dataSources: pr_ds: writeDataSourceName: ds_0_write readDataSourceNames: [ds_0_read_0, ds_0_read_1]

5.5 现象:应用启动时报java.lang.NoClassDefFoundError: org/apache/shardingsphere/infra/route/context/RouteContext

原因:Maven 依赖传递冲突,shardingsphere-jdbc-core-spring-boot-starter与shardingsphere-jdbc-governance-spring-boot-starter同时引入,后者包含旧版 infra 包。
解决:

  • mvn dependency:tree | grep shardingsphere查冲突;
  • 在pom.xml中exclusion掉冲突包:
<exclusion> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-infra-common</artifactId> </exclusion>

6. 生产验证与兜底技巧:用这 3 个命令和 1 个脚本,守住你的分库分表生命线

6.1 验证分片路由是否精准:shardingSphere.metrics+ Prometheus + Grafana

ShardingSphere 5.3.2 暴露完整 metrics,无需额外埋点。在application.yml中开启:

spring: shardingsphere: props: metrics.enabled: true metrics.prometheus.host: 0.0.0.0 metrics.prometheus.port: 9191

然后用 Prometheus 抓取,重点关注三个指标:

指标名含义健康阈值告警建议
shardingsphere_routing_count_total{type="database"}库路由次数每秒 < 500> 1000 次/秒且持续 5 分钟,触发“路由风暴”告警
shardingsphere_broadcast_count_total广播查询次数= 0> 0 即表示有 SQL 未带分片键,立即排查日志
shardingsphere_actual_sql_count_total实际执行 SQL 数(含分片后)≈ 逻辑 SQL 数 × 分片数若远大于此值,说明有笛卡尔积或未绑定表

技巧:在 Grafana 中建面板,用rate(shardingsphere_broadcast_count_total[1h]) > 0做告警,第一时间发现“裸奔查询”。

6.2 检查分片数据均衡性:一条 SQL 查清各分片数据量

在任意一个分片库(如order_db_0)中执行:

SELECT 'ds_0' as db_name, table_name, table_rows, round(((data_length + index_length) / 1024 / 1024), 2) as size_mb FROM information_schema.TABLES WHERE table_schema = 'order_db_0' AND table_name LIKE 't_order_%' UNION ALL SELECT 'ds_1' as db_name, table_name, table_rows, round(((data_length + index_length) / 1024 / 1024), 2) as size_mb FROM information_schema.TABLES WHERE table_schema = 'order_db_1' AND table_name LIKE 't_order_%' -- 依此类推 ds_2, ds_3 ORDER BY db_name, table_name;

判断标准:各table_rows标准差 < 10%,size_mb差异 < 15%。若ds_0.t_order_0有 500 万行,而ds_0.t_order_1只有 80 万行,说明表路由算法有 bug,需检查(user_id / 4) % 8计算逻辑。

6.3 兜底降级脚本:当分片中间件崩溃时,一键切回单库模式

我们写了一个 Bash 脚本switch-to-single-db.sh,放在运维平台一键执行:

#!/bin/bash # 切换单库模式:停用 ShardingSphere,直连 order_db_0 APP_PID=$(pgrep -f "java.*OrderApplication") if [ -z "$APP_PID" ]; then echo "App not running" exit 1 fi # 1. 修改配置中心(Nacos)的 dataId: order-service.yaml curl -X POST "http://nacos:8848/nacos/v1/cs/configs?dataId=order-service.yaml&group=DEFAULT_GROUP" \ -H "Content-Type: text/plain" \ -d "spring: datasource: url: jdbc:mysql://127.0.0.1:3306/order_db_0?useSSL=false username: root password: 123456" # 2. 重启应用(优雅停机) kill -15 $APP_PID sleep 10 # 3. 验证连接 nc -z 127.0.0.1 3306 && echo "Single DB mode activated" || echo "Failed"

为什么有效:我们所有 DAO 层代码都基于JdbcTemplate或MyBatis,未强依赖 ShardingSphere 的ShardingSphereDataSource。只要DataSourceBean 换成普通 Druid,业务代码零修改即可运行(性能下降,但可用)。

最后说句实在话:分库分表不是终点,而是起点。我见过太多团队花三个月上线分片,结果因为没做t_order_item的绑定表

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询