ShardingSphere:分库分表实战
随着业务的发展,单表数据量突破千万级后,查询性能会急剧下降。这时候,分库分表就成了必不可少的解决方案。本文将基于Apache ShardingSphere 5.x版本,通过一个完整的订单系统示例,详细讲解如何实现分库分表,包括订单的增删改查操作。
一、ShardingSphere简介
Apache ShardingSphere是一套开源的分布式数据库中间件解决方案,由JDBC、Proxy和Sidecar(规划中)这3款既能够独立部署,又支持混合部署配合使用的产品组成。它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。
核心特性
数据分片:支持分库分表,提供多种分片策略
读写分离:支持多种读写分离策略
分布式事务:提供XA和BASE两种分布式事务解决方案
数据库治理:配置动态化、数据脱敏、SQL监控等
二、分库分表方案设计
2.1 分片策略
在订单系统中,我们采用以下分片策略:
分库策略:根据user_id进行取模运算
user_id % 2 = 0 → demo_ds_0
user_id % 2 = 1 → demo_ds_1
分表策略:根据order_id进行取模运算
order_id % 2 = 0 → t_order_0 / t_order_item_0
order_id % 2 = 1 → t_order_1 / t_order_item_1
2.2 表结构设计
订单表(t_order)
CREATE TABLE`t_order_0` (
`order_id`BIGINTNOTNULLCOMMENT'订单ID',
`user_id`BIGINTNOTNULLCOMMENT'用户ID',
`order_no`VARCHAR(64) NOTNULLCOMMENT'订单编号',
`total_amount`DECIMAL(10,2) NOTNULLCOMMENT'订单总金额',
`status`TINYINTNOTNULLDEFAULT0COMMENT'订单状态',
`address`VARCHAR(255) NOTNULLCOMMENT'收货地址',
`phone`VARCHAR(20) NOTNULLCOMMENT'联系电话',
`receiver_name`VARCHAR(64) NOTNULLCOMMENT'收货人',
`create_time` DATETIME NOTNULLDEFAULTCURRENT_TIMESTAMP,
`update_time` DATETIME NOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,
PRIMARY KEY (`order_id`),
KEY`idx_user_id` (`user_id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;
订单项表(t_order_item)
CREATE TABLE`t_order_item_0` (
`item_id`BIGINTNOTNULLCOMMENT'订单项ID',
`order_id`BIGINTNOTNULLCOMMENT'订单ID',
`product_id`BIGINTNOTNULLCOMMENT'商品ID',
`product_name`VARCHAR(128) NOTNULLCOMMENT'商品名称',
`unit_price`DECIMAL(10,2) NOTNULLCOMMENT'商品单价',
`quantity`INTNOTNULLCOMMENT'购买数量',
`total_price`DECIMAL(10,2) NOTNULLCOMMENT'小计金额',
PRIMARY KEY (`item_id`),
KEY`idx_order_id` (`order_id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;
2.3 绑定表配置
订单表和订单项表配置为绑定表,这样在关联查询时会路由到同一个分片,避免跨库Join:
sharding:
binding-tables:
- t_order,t_order_item
三、项目搭建
3.1 Maven依赖
<properties>
<sharding-sphere.version>5.3.2</sharding-sphere.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
</properties>
<dependencies>
<!-- ShardingSphere-JDBC -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
</dependencies>
3.2 核心配置
spring:
shardingsphere:
# 数据源配置
datasource:
names:ds0,ds1
ds0:
type:com.alibaba.druid.pool.DruidDataSource
driver-class-name:com.mysql.cj.jdbc.Driver
url:jdbc:mysql://localhost:3306/demo_ds_0?useSSL=false&serverTimezone=Asia/Shanghai
username:root
password:root
ds1:
type:com.alibaba.druid.pool.DruidDataSource
driver-class-name:com.mysql.cj.jdbc.Driver
url:jdbc:mysql://localhost:3306/demo_ds_1?useSSL=false&serverTimezone=Asia/Shanghai
username:root
password:root
# 分片规则配置
rules:
sharding:
# 绑定表
binding-tables:
-t_order,t_order_item
# 分片表配置
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:table-mod
t_order_item:
actual-data-nodes:ds$->{0..1}.t_order_item_$->{0..1}
database-strategy:
standard:
sharding-column:order_id
sharding-algorithm-name:order-db-mod
table-strategy:
standard:
sharding-column:order_id
sharding-algorithm-name:table-mod
# 分片算法
sharding-algorithms:
db-mod:
type:MOD
props:
sharding-count:2
table-mod:
type:MOD
props:
sharding-count:2
四、订单CRUD操作实现
4.1 实体类定义
@Data
@Builder
@TableName("t_order")
public class Order {
@TableId(type = IdType.ASSIGN_ID)
private Long orderId;
private Long userId;
private String orderNo;
private BigDecimal totalAmount;
private Integer status;
private String address;
private String phone;
private String receiverName;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
4.2 创建订单
Service实现
@Transactional(rollbackFor = Exception.class)
public Long createOrder(Long userId, BigDecimal totalAmount,
String address, String phone, String receiverName) {
// 构建订单对象
Order order = Order.builder()
.userId(userId)
.orderNo(generateOrderNo())
.totalAmount(totalAmount)
.status(0) // 待支付
.address(address)
.phone(phone)
.receiverName(receiverName)
.createTime(LocalDateTime.now())
.updateTime(LocalDateTime.now())
.build();
// 保存订单 - ShardingSphere会根据user_id进行分库
orderMapper.insert(order);
log.info("订单创建成功,订单ID: {}, 路由到: ds{}.t_order_{}",
order.getOrderId(),
userId % 2,
order.getOrderId() % 2);
return order.getOrderId();
}
路由过程:
SQL解析:解析INSERT语句,提取分片键user_id
SQL路由:计算user_id % 2,确定目标数据库
SQL改写:将逻辑表名t_order改为物理表名t_order_0或t_order_1
SQL执行:在目标分片执行INSERT操作
结果归并:返回执行结果
4.3 查询订单
根据订单ID精确查询
public Order getOrderByOrderId(Long orderId) {
// 根据order_id精确路由
Order order = orderMapper.selectById(orderId);
if (order != null) {
log.info("查询成功,订单所在分片: ds{}.t_order_{}",
order.getUserId() % 2,
orderId % 2);
}
return order;
}
根据用户ID查询订单列表
public List<Order> getOrdersByUserId(Long userId) {
// ShardingSphere会根据user_id路由到对应数据库
// 然后查询该库下所有分表
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Order::getUserId, userId)
.orderByDesc(Order::getCreateTime);
return orderMapper.selectList(wrapper);
}

4.4 更新订单
@Transactional(rollbackFor = Exception.class)
public boolean updateOrderStatus(Long orderId, Integer status) {
Order order = Order.builder()
.orderId(orderId)
.status(status)
.updateTime(LocalDateTime.now())
.build();
int rows = orderMapper.updateById(order);
log.info("更新完成,影响行数: {}", rows);
return rows > 0;
}
4.5 删除订单
@Transactional(rollbackFor = Exception.class)
public boolean deleteOrder(Long orderId) {
// 先查询订单信息,确定分片
Order order = orderMapper.selectById(orderId);
if (order == null) {
returnfalse;
}
// 删除订单项
orderItemService.deleteByOrderId(orderId);
// 删除订单
int rows = orderMapper.deleteById(orderId);
return rows > 0;
}

五、分片算法详解
5.1 MOD取模算法
最常用的分片算法,计算简单,数据分布均匀:
table-mod:
type: MOD
props:
sharding-count: 2 # 分片数量
5.2 INLINE行表达式算法
支持复杂分片规则:
inline-strategy:
type: INLINE
props:
algorithm-expression: ds_${user_id % 2}
5.3 STANDARD标准分片算法
支持自定义分片规则:
public class ModShardingAlgorithm implements StandardShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<Long> shardingValue) {
Long value = shardingValue.getValue();
int index = (int) (value % 2);
for (String name : availableTargetNames) {
if (name.endsWith(String.valueOf(index))) {
return name;
}
}
thrownew IllegalArgumentException("无法路由到目标数据源");
}
}
六、Docker一键部署

6.1 docker-compose.yml
version: '3.8'
services:
mysql-ds0:
image:mysql:8.0
container_name:sharding-mysql-ds0
environment:
MYSQL_ROOT_PASSWORD:root
MYSQL_DATABASE:demo_ds_0
ports:
-"3307:3306"
volumes:
-./init-sql:/docker-entrypoint-initdb.d
mysql-ds1:
image:mysql:8.0
container_name:sharding-mysql-ds1
environment:
MYSQL_ROOT_PASSWORD:root
MYSQL_DATABASE:demo_ds_1
ports:
-"3308:3306"
volumes:
-./init-sql:/docker-entrypoint-initdb.d
sharding-app:
build:.
container_name:shardingphere-app
ports:
-"8080:8080"
depends_on:
-mysql-ds0
-mysql-ds1
七、生产实践
7.1 分片键选择
选择原则:
选择查询频率高的字段作为分片键
避免使用随机性强的字段
考虑数据增长趋势和分布
常见方案:
用户表:user_id
订单表:user_id(分库)+ order_id(分表)
商品表:category_id + product_id
7.2 性能优化
1. 使用广播表
broadcast-tables:
- t_config # 配置表,每个库都保存完整数据
2. 避免全路由查询
// 不推荐:查询所有订单
List<Order> all = orderMapper.selectList(null);
// 推荐:指定分片键查询
List<Order> orders = orderMapper.selectList(
new LambdaQueryWrapper<Order>()
.eq(Order::getUserId, 1001L)
);
3. 合理使用绑定表将经常关联查询的表配置为绑定表,避免跨库Join:
binding-tables:
- t_order,t_order_item
- t_user,t_user_profile
7.3 监控告警
启用SQL日志监控慢查询:
spring:
shardingsphere:
props:
sql-show: true # 显示SQL
八、总结
本文通过一个完整的订单系统示例,详细讲解了ShardingSphere分库分表的实现方案,包括:
分库分表方案设计
ShardingSphere配置与使用
订单CRUD操作实现
Docker容器化部署
生产环境最佳实践
核心要点:
合理选择分片键是关键
利用绑定表避免跨库查询
生产环境要做好容量规划
监控和性能优化不可忽视
更多推荐




所有评论(0)