Spring Boot Shardingsphere 分表
在高并发业务场景中,单表数据量激增会导致查询性能急剧下降。分表作为解决这一问题的经典方案,能将数据分散到多个物理表中,提升数据库吞吐量。本文将详细讲解如何在 Spring Boot 项目中集成 ShardingSphere 实现分表,并深入剖析分表策略、配置细节及最佳实践。
一、分表背景与 ShardingSphere 简介
1. 为什么需要分表?
- 性能瓶颈:单表数据量达到千万级以上时,索引失效、查询缓慢等问题凸显。
- 维护困难:大表的 DDL 操作(如加字段、加索引)会锁表,影响业务可用性。
- 扩展性差:单表无法利用分布式存储的优势,硬件升级成本高。
2. ShardingSphere 是什么?
ShardingSphere 是一款开源的分布式数据库中间件,提供数据分片(分库分表)、读写分离、分布式事务等核心功能。其分表能力通过透明化的方式实现,应用层无需感知物理表的存在,只需操作逻辑表即可。
二、环境准备
1. 技术栈版本
- JDK:1.8 及以上
- Spring Boot:2.7.x(兼容 3.x)
- ShardingSphere:5.4.0(最新稳定版)
- 数据库:MySQL 8.0
- 构建工具:Maven
- ORM 框架:MyBatis-Plus(简化 CRUD 操作)
2. 依赖配置
在 pom.xml
中添加核心依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <!-- ShardingSphere 核心依赖 -->
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.4.0</version> </dependency>
<!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
<!-- MyBatis-Plus(可选,简化数据访问层) --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>
<!-- 数据源连接池 --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency>
|
三、分表核心配置详解
1. 数据库与表设计
以「订单表」为例,假设需按 order_id
分表,分散到 2 个物理表中:
- 逻辑表名:
t_order
(应用层操作的表名)
- 物理表名:
t_order_0
、t_order_1
(实际存储数据的表)
创建物理表 SQL:
1 2 3 4 5 6 7 8 9 10 11 12
| -- 物理表 0 CREATE TABLE `t_order_0` ( `order_id` bigint NOT NULL COMMENT '订单ID(分片键)', `user_id` bigint NOT NULL COMMENT '用户ID', `amount` decimal(10,2) NOT NULL COMMENT '订单金额', `create_time` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`order_id`), KEY `idx_user_id` (`user_id`) COMMENT '用户ID索引' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表(分片0)';
-- 物理表 1(表结构与 t_order_0 完全一致) CREATE TABLE `t_order_1` LIKE `t_order_0`;
|
2. 核心配置文件(application.yml
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| spring: shardingsphere: # 运行模式(开发环境用内存模式,生产可用集群模式) mode: type: Memory repository: type: JDBC # 元数据持久化方式(可选) # 数据源配置(支持多数据源,此处单数据源示例) datasource: names: ds0 # 数据源名称,多数据源用逗号分隔 ds0: type: com.zaxxer.hikari.HikariDataSource # 连接池类型 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/sharding_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true username: root password: root123 hikari: maximum-pool-size: 10 # 连接池最大连接数 idle-timeout: 30000 # 空闲连接超时时间(毫秒) # 分表规则配置 rules: sharding: # 逻辑表配置 tables: t_order: # 逻辑表名(与代码中实体类对应) actual-data-nodes: ds0.t_order_$->{0..1} # 物理表路径:数据源.表名_索引 table-strategy: # 分表策略 standard: # 标准分片策略(适用于单分片键) sharding-column: order_id # 分片键(用于计算分表的字段) sharding-algorithm-name: order_inline # 关联的分片算法 key-generate-strategy: # 主键生成策略(解决分表主键冲突) column: order_id # 主键字段名 key-generator-name: snowflake # 主键生成器名称 # 分片算法定义 sharding-algorithms: order_inline: # 与 table-strategy 中配置的名称一致 type: INLINE # 行表达式分片算法(简单场景推荐) props: algorithm-expression: t_order_$->{order_id % 2} # 分表公式:order_id取模2 # 主键生成器配置(分布式唯一ID) key-generators: snowflake: # 与 key-generate-strategy 中配置的名称一致 type: SNOWFLAKE # 雪花算法(生成64位唯一ID) props: worker-id: 1 # 工作节点ID(集群部署时需保证唯一,范围0-31) max-tolerate-time-diff-milliseconds: 1000 # 最大容忍时间差(毫秒) # 全局属性配置 props: sql-show: true # 打印路由后的实际SQL(调试必备) sql-comment-parse-enabled: false # 是否解析SQL注释 executor-size: 10 # 执行器线程池大小
|
3. 配置参数详解
配置项 |
作用说明 |
actual-data-nodes |
定义物理表集合,$->{0..1} 表示生成 t_order_0 和 t_order_1 两张表 |
sharding-column |
分片键,必须是表中存在的字段(如 order_id ),决定数据路由到哪个表 |
algorithm-expression |
分表公式,order_id % 2 表示按订单 ID 取模 2,数据平均分散到 2 张表 |
key-generator |
分布式主键生成器,避免多表主键冲突(雪花算法支持高并发场景) |
sql-show |
开启后可在日志中查看逻辑 SQL 和路由后的物理 SQL,便于调试 |
三、代码实现
1. 实体类(Order.java
)
1 2 3 4 5 6 7 8 9 10 11 12
| import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @TableName("t_order") // 关联逻辑表名 public class Order { private Long orderId; // 订单ID(分片键+主键) private Long userId; // 用户ID private BigDecimal amount; // 订单金额 private LocalDateTime createTime; // 创建时间 }
|
2. Mapper 接口(OrderMapper.java
)
1 2 3 4 5 6 7
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import com.example.entity.Order; @Mapper public interface OrderMapper extends BaseMapper<Order> { // 继承BaseMapper后,无需手动编写CRUD方法(MyBatis-Plus提供) }
|
3. 业务层实现(OrderService.java
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.entity.Order; import com.example.mapper.OrderMapper; import java.time.LocalDateTime; @Service public class OrderService extends ServiceImpl<OrderMapper, Order> { /** * 创建订单(自动路由到对应分表) */
public void createOrder(Order order) { order.setCreateTime(LocalDateTime.now()); baseMapper.insert(order); // 插入操作,ShardingSphere自动路由 }
/** * 根据订单ID查询(自动路由到对应分表) */
public Order getOrderById(Long orderId) { return baseMapper.selectById(orderId); // 查询操作,自动定位物理表 }
}
|
四、测试验证
1. 测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import com.example.entity.Order; import com.example.service.OrderService; import java.math.BigDecimal; @SpringBootTest public class ShardingTableTest {
@Autowired private OrderService orderService;
@Test public void testShardingInsert() {
// 插入测试数据(order_id为偶数路由到t_order_0,奇数路由到t_order_1)
Order order1 = new Order(); order1.setOrderId(1001L); // 1001 % 2 = 1 → t_order_1 order1.setUserId(100L); order1.setAmount(new BigDecimal("99.99")); orderService.createOrder(order1);
Order order2 = new Order(); order2.setOrderId(1002L); // 1002 % 2 = 0 → t_order_0 order2.setUserId(100L); order2.setAmount(new BigDecimal("199.99")); orderService.createOrder(order2);
}
@Test public void testShardingQuery() { Order order = orderService.getOrderById(1001L); System.out.println("查询结果:" + order); // 自动从t_order_1查询
}
}
|
2. 日志验证(sql-show: true
开启后)
1 2 3 4 5 6 7 8 9
| # 插入order1(order_id=1001)的日志 Logic SQL: INSERT INTO t_order (order_id, user_id, amount, create_time) VALUES (?, ?, ?, ?) Actual SQL: ds0 ::: INSERT INTO t_order_1 (order_id, user_id, amount, create_time) VALUES (1001, 100, 99.99, '2024-05-01 10:00:00') # 插入order2(order_id=1002)的日志 Logic SQL: INSERT INTO t_order (order_id, user_id, amount, create_time) VALUES (?, ?, ?, ?) Actual SQL: ds0 ::: INSERT INTO t_order_0 (order_id, user_id, amount, create_time) VALUES (1002, 100, 199.99, '2024-05-01 10:00:01') # 查询order_id=1001的日志 Logic SQL: SELECT * FROM t_order WHERE order_id = ? Actual SQL: ds0 ::: SELECT * FROM t_order_1 WHERE order_id = 1001
|
从日志可见,ShardingSphere 已根据 order_id
自动路由到对应的物理表,实现了分表逻辑的透明化。
五、进阶分表策略
1. 范围分片(按时间分表)
适用于按时间维度分表的场景(如订单表按月份分表):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| rules: sharding: tables: t_order: actual-data-nodes: ds0.t_order_$->{202401..202412} # 按月份分表 table-strategy: standard: sharding-column: create_time # 分片键(时间字段) sharding-algorithm-name: order_range sharding-algorithms: order_range: type: RANGE # 范围分片算法 props: sharding-rules: 2024-02-01=t_order_202401, 2024-03-01=t_order_202402 # 时间范围映射
|
2. 哈希分片(按用户 ID 分表)
适用于按用户 ID 等字段均匀分表的场景:
1 2 3 4 5
| sharding-algorithms: order_hash: type: HASH_MOD # 哈希取模算法 props: sharding-count: 4 # 分表数量(4张表)
|
3. 复合分片(多字段分表)
当单字段分表无法满足需求时,可使用多字段联合分表(如 user_id
+ order_id
):
1 2 3 4 5 6 7 8 9 10 11
| tables: t_order: table-strategy: complex: # 复合分片策略 sharding-columns: user_id, order_id # 多个分片键 sharding-algorithm-name: order_complex sharding-algorithms: order_complex: type: COMPLEX_INLINE props: algorithm-expression: t_order_${user_id % 2}_${order_id % 2} # 先按user_id取模,再按order_id取模
|
六、注意事项与最佳实践
1. 分片键选择原则
- 高频查询字段优先:如订单查询频繁使用
order_id
,用户数据查询频繁使用 user_id
。
- 避免随机性:如使用
UUID
作为分片键会导致数据分布不均,查询性能下降。
- 稳定性:分片键字段不应被更新(如
order_id
一旦生成不修改,而 status
状态字段不适合作为分片键)。
2. 表结构一致性
3. 跨表操作限制
4. 数据迁移与扩容
- 历史数据迁移:分表前的历史数据需按分片规则迁移到对应物理表,可使用 ShardingSphere 的
Migration
工具。
- 水平扩容:当单表数据量再次达到瓶颈时,可增加物理表数量(如从 2 张表扩容到 4 张表),并调整分片算法(如
order_id % 4
)。
5. 性能优化建议
- 索引优化:在分片键和高频查询字段上建立索引(如
t_order_0
的 order_id
主键索引、user_id
普通索引)。
- SQL 优化:避免
SELECT *
,只查询必要字段;分页查询时尽量携带分片键,减少全表扫描。
- 连接池调优:根据并发量调整连接池大小,避免连接不足或资源浪费。
七、总结
通过 ShardingSphere 实现分表后,应用层可像操作单表一样操作分表,大幅降低了分表逻辑的复杂度。核心步骤包括:
- 设计物理表结构,确定逻辑表与物理表的映射关系。
- 配置数据源、分表策略(分片键、分片算法)和主键生成器。
- 编写实体类、Mapper 和 Service 代码,透明化分表逻辑。
- 选择合适的分片算法(如 inline、range、hash),并遵循分片键选择原则。
合理的分表策略能有效解决单表性能瓶颈,是高并发系统的必备技术之一。在实际落地时,需结合业务场景选择分片键和算法,并做好数据迁移、扩容和监控工作,确保系统稳定运行。