当单库单表的数据量突破千万、写入 QPS 顶到 MySQL 上限时,简单地”加从库”只能缓解读压力,却解决不了写入瓶颈和存储天花板。ShardingSphere 是一套 Apache 开源的数据库中间件,用分库分表把数据按规则打散到多个库与表,用读写分离把查询流量导向从库,让业务层几乎不用改 SQL 就能完成横向扩展。本文基于 ShardingSphere 5.x,从核心概念到 Spring Boot 落地,给你一套可直接复用的配置。
一、为什么需要 ShardingSphere
数据库扩展通常分两步。第一步是垂直拆分:把大表拆到独立库、把读压力分到从库。但当单表行数继续膨胀到几千万,索引树变深、B+ 树层级增加,即便是主键查询也会变慢,单实例的 CPU 和磁盘 IO 也终会触顶。水平拆分(分库分表)才是最终解法——把一张逻辑表按分片键切成多张物理表,分散在不同库的不同实例上。
难点在于,手写路由逻辑会让所有 DAO 层代码“中毒”:每个 SQL 都要先算库和表名。ShardingSphere 的价值正是在这一层做无侵入拦截——你照常写 INSERT INTO t_order ...,它在 JDBC 驱动或代理层帮你改写 SQL、路由到正确的库表,再把结果归并回来。
二、两种接入模式:JDBC 与 Proxy
ShardingSphere 提供两种形态,适用场景不同:
| 维度 | ShardingSphere-JDBC | ShardingSphere-Proxy |
|---|---|---|
| 部署位置 | 嵌入应用进程(Jar 包) | 独立服务,对应用透明 |
| 性能 | 无网络跳转,损耗最小 | 多一跳网络,略有损耗 |
| 异构语言 | 仅 Java | 任意语言(MySQL/PG 协议) |
| 运维成本 | 配置随应用发布 | 集中管理,统一治理 |
| 适合场景 | Java 单体/微服务 | 多语言、需统一管控的平台 |
对绝大多数 Spring Boot 团队,JDBC 模式是首选:零额外组件、性能最好、配置即代码。下文全部以 JDBC 模式演示。
三、环境准备:Docker 起两套分片库
用 Docker Compose 起两个 MySQL 实例,分别代表两个分片库 ds0 与 ds1。每个库里都预先建好 t_order_0、t_order_1 两张物理表。
version: "3.8"
services:
ds0:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: order_db0
ports: ["3306:3306"]
ds1:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: order_db1
ports: ["3307:3306"]
生产环境里,每个分片通常还配一主多从做读写分离;下面第六节的配置会展示如何在分片之上叠加读写分离。
四、核心概念:分片键、逻辑表与分片算法
动手前先厘清三个关键概念:
- 逻辑表:业务 SQL 里写的表名,例如
t_order。 - 真实表:物理落库表,例如
ds0.t_order_0、ds1.t_order_1。 - 分片键(sharding key):决定数据落到哪里的列,如
user_id、order_id。
常见分片策略各有取舍:
| 策略 | 算法 | 优点 | 注意点 |
|---|---|---|---|
| 取模分片 | MOD | 数据均匀、实现简单 | 扩容需迁移数据 |
| 范围分片 | RANGE | 范围查询友好、易扩容 | 易产生热点 |
| 哈希分片 | HASH / INLINE | 分布均匀、可控 | 分片数固定 |
| 时间分片 | 按年/月/日 | 天然冷热分离 | 近期库压力大 |
五、实战:订单表分库分表配置
场景:t_order 按 user_id 取模分 2 个库,按 order_id 取模分 2 张表,最终形成 ds{0..1}.t_order_{0..1} 共 4 张物理表。
5.1 引入依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.4.1</version>
</dependency>
5.2 分片规则配置
spring:
shardingsphere:
datasource:
names: ds0, ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db0
username: root
password: root
ds1:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://127.0.0.1:3307/order_db1
username: root
password: root
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds${0..1}.t_order_${0..1}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db-mod
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: tbl-mod
sharding-algorithms:
db-mod:
type: MOD
props:
sharding-count: 2
tbl-mod:
type: INLINE
props:
algorithm-expression: t_order_${order_id % 2}
props:
sql-show: true
5.3 业务代码零改造
配置完成后,你的 Repository 完全不用感知分片。ShardingSphere 在 JDBC 层拦截并重写 SQL:
@Insert("INSERT INTO t_order (order_id, user_id, amount) VALUES (#{o}, #{u}, #{a})")
void create(@Param("o") long orderId, @Param("u") long userId, @Param("a") BigDecimal amount);
// user_id=101, order_id=2001
// 路由:db-mod(101) -> ds1;tbl-mod(2001) -> t_order_1
// 实际执行:INSERT INTO ds1.t_order_1 ...
即便同一查询跨多个分片,框架也会并行下发、归并结果,对上层透明。这也是它和Vitess 这类基于 MySQL 协议的分片方案最大的体验差异:对应用而言仍是普通 JDBC。
六、读写分离:一主多从与负载均衡
分库分表解决”写”和”容量”,读写分离则进一步把 SELECT 打到从库。在分片规则之外叠加 readwrite-splitting 规则即可:
spring:
shardingsphere:
rules:
readwrite-splitting:
data-sources:
ds0:
type: Static
props:
write-data-source-name: ds0_master
read-data-source-names: ds0_slave0, ds0_slave1
load-balancer-name: round-robin
load-balancers:
round-robin:
type: ROUND_ROBIN
负载均衡算法可按场景切换:
| 算法 | 行为 | 适用 |
|---|---|---|
| ROUND_ROBIN | 轮询从库 | 从库性能一致 |
| RANDOM | 随机选从库 | 从库差异小 |
| WEIGHT | 按权重分发 | 从库配置不均 |
注意:读写分离默认不保证读已写。主从复制有延迟,刚写入就立即读可能读到旧值。对强一致要求的场景,要么走主库 hint,要么用延迟更低的半同步复制。
七、分布式事务:XA 与 Seata 柔性事务
跨分片写入若涉及多个库,本地事务失效。ShardingSphere 内置 XA(两阶段提交)与基于Seata 的 AT 模式柔性事务两种方案:
spring:
shardingsphere:
rules:
sharding:
...
props:
transaction:
default-type: XA
provider-type: Atomikos
在代码里用注解切换类型:
@ShardingTransactionType(TransactionType.XA)
@Transactional
public void crossShardTransfer() {
orderMapper.insert(...); // 落 ds0
accountMapper.update(...); // 落 ds1
}
XA 保证强一致但锁资源久;高并发写场景推荐 Seata AT 这类最终一致方案,吞吐更高。选型时可参考分布式事务实战里的对照。
八、分页、分布式 ID 与绑定表
三个上线前必处理的细节:
- 深度分页:
LIMIT 100000,10会向每个分片下发再归并,代价极高。应改用”上一页最大 ID + 游标”或业务游标分页。 - 分布式 ID:自增主键在分片下会冲突,务必用雪花算法或 UUID。ShardingSphere 内置
雪花算法主键生成器。 - 绑定表(Binding Table):
t_order与按同一分片键分片时,声明为绑定表可避免冗余笛卡尔积,让关联查询只在同分片内执行。
spring:
shardingsphere:
rules:
sharding:
binding-tables:
- t_order, t_order_item
key-generators:
snowflake:
type: SNOWFLAKE
九、生产落地检查清单
| 项 | 检查要点 |
|---|---|
| 分片键 | 选高频等值查询列,避免 NULL 与频繁更新 |
| 扩容预案 | 取模分片迁移成本高,提前规划翻倍扩容脚本 |
| 主从延迟 | 监控 Seconds_Behind_Master,强一致读走主库 |
| 分布式事务 | 明确 XA / Seata 边界,防止长事务锁表 |
| 慢 SQL | 开启 sql-show,结合PG/MySQL 慢查询优化手段排查 |
| 容量基线 | 单分片建议控制在千万级,超阈值提前再拆 |
十、小结
ShardingSphere 把分库分表、读写分离、分布式事务打包成”对业务透明”的能力:JDBC 模式零侵入接入 Spring Boot,Proxy 模式统一治理多语言服务。它不是银弹——分片键设计、扩容迁移、主从延迟仍是架构师要操心的事,但有了这套中间件,你至少能把精力放在数据模型本身,而不是和路由 SQL 纠缠。下一步可以结合CI/CD把分片配置纳入版本管理,让每一次扩容都可回滚、可审计。




