单表数据量超过500万行后查询性能急剧下降,B+树层级增加导致磁盘IO放大。分库分表将数据分散到多个物理库表,通过路由规则定位数据位置。ShardingSphere-JDBC以JDBC驱动方式嵌入应用,无需独立代理进程,网络开销为零。
分库分表方案选型与架构设计
订单系统按用户ID取模分库、按订单创建时间分表的混合方案。8个库每个库16张表,总计128张物理表,单表数据量控制在200万以内。
# 分片规划
# 分库规则:user_id % 8 -> ds_0 ~ ds_7
# 分表规则:order_date的月份 % 16 -> order_0 ~ order_15
#
# 物理库:ds_0, ds_1, ..., ds_7
# 物理表:t_order_0, t_order_1, ..., t_order_15
# 路由示例:user_id=1001, order_date=2026-07
# 库:1001 % 8 = 1 -> ds_1
# 表:(202607 % 16) = 7 -> t_order_7
分片键选择遵循原则:高频查询条件字段、数据分布均匀、避免跨片查询。订单系统80%查询携带user_id,适合作为分库键。按时间分表便于历史数据归档和冷热分离。
ShardingSphere-JDBC集成与分片配置
Maven依赖:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc</artifactId>
<version>5.5.0</version>
</dependency>
application.yml配置:
spring:
datasource:
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
url: jdbc:shardingsphere:classpath:sharding-config.yaml
# sharding-config.yaml
dataSources:
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
props:
jdbcUrl: jdbc:mysql://192.168.1.10:3306/order_db_0?useSSL=true
username: root
password: ${DB_PASS}
maximumPoolSize: 20
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
props:
jdbcUrl: jdbc:mysql://192.168.1.11:3306/order_db_1?useSSL=true
username: root
password: ${DB_PASS}
maximumPoolSize: 20
# ... ds_2 ~ ds_7
rules:
- !SHARDING
tables:
t_order:
actualDataNodes: ds_${0..7}.t_order_${0..15}
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: db_mod
tableStrategy:
standard:
shardingColumn: order_date
shardingAlgorithmName: table_month_mod
keyGenerateStrategy:
column: order_id
keyGeneratorName: snowflake
shardingAlgorithms:
db_mod:
type: MOD
props:
sharding-count: 8
table_month_mod:
type: INLINE
props:
algorithm-expression: t_order_${Integer.parseInt(order_date.toString().replace('-','')) % 16}
keyGenerators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 1
SNOWFLAKE生成全局唯一ID,worker-id需在集群中唯一。INLINE表达式支持Groovy语法,可编写复杂分片逻辑。MOD算法简单高效,但不支持动态扩容。如需后续扩容,推荐使用一致性哈希算法。
分布式主键生成与跨片查询处理
// MyBatis-Plus实体配置
@TableName("t_order")
public class Order {
@TableId(type = IdType.ASSIGN_ID) // 使用ShardingSphere分配ID
private Long orderId;
private Long userId;
private String orderDate;
private BigDecimal amount;
private Integer status;
}
// 分片键查询:直接路由到单库单表,性能最优
Order order = orderMapper.selectOne(
new LambdaQueryWrapper<Order>()
.eq(Order::getUserId, 1001L)
.eq(Order::getOrderId, 999888777L)
);
// 非分片键查询:广播到所有分片,合并结果
List<Order> orders = orderMapper.selectList(
new LambdaQueryWrapper<Order>()
.eq(Order::getStatus, 2)
.ge(Order::getOrderDate, "2026-07-01")
);
// 实际执行8*16=128条SQL,ShardingSphere自动合并结果
非分片键查询触发全路由广播,性能极差。解决方案:建立ElasticSearch二级索引,或将高频查询字段冗余到分片键组合中。分页查询跨片时,ShardingSphere采用流式归并+改写SQL的方式,将LIMIT 10, 20改写为LIMIT 0, 30在各分片执行后内存归并取第10-20条。
数据迁移与双写扩容方案
从单库迁移到分库分表采用双写+数据同步+灰度切分三阶段方案:
# 阶段1:双写(新旧库同时写入)
// 使用ShardingSphere的读写分离+影子库
// 或在应用层AOP拦截写入操作,同步到新分片库
@Around("execution(* com.example.mapper.OrderMapper.*(..))")
public Object dualWrite(ProceedingJoinPoint pjp) throws Throwable {
Object result = pjp.proceed(); // 写旧库
// 异步写新分片库
shardingOrderMapper.insert((Order) pjp.getArgs()[0]);
return result;
}
# 阶段2:数据全量同步+增量同步
# 使用DataX或Canal同步历史数据
# 全量同步
python datax.py --job order_migration.json
# 增量同步(Canal监听binlog)
# canal订阅旧库binlog,实时同步到新分片库
# 阶段3:灰度切分
# 按user_id取模灰度,10% -> 30% -> 50% -> 100%
# 配置开关控制路由
@Value("${sharding.enable:false}")
private boolean shardingEnable;
public Order queryOrder(Long userId, Long orderId) {
if (shardingEnable && userId % 10 < grayPercent) {
return shardingOrderMapper.selectById(orderId);
}
return legacyOrderMapper.selectById(orderId);
}
数据校验使用CRC32或MD5对比新旧库数据一致性。全量同步完成后,对比关键表的数据量和抽样数据MD5值。增量同步延迟控制在1秒以内,通过Canal的checkpoint机制保证不丢数据。灰度切分期间保留回滚能力,发现问题可秒级切回旧库。
分库分表后的运维要点
分库分表后DDL操作需通过ShardingSphere的Scaling工具统一执行。在线DDL支持在不停止服务的情况下修改表结构。分布式事务使用XA或BASE模式:XA保证强一致性但性能较差,BASE(最终一致性)通过Seata的TCC/SAGA模式实现,适合高并发场景。定期清理历史数据通过DROP TABLE直接删除物理表,比DELETE高效且不产生binlog。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-fen-ku-fen-biao-shi-zhan-shardingspherejdbc-shui-ping/