视频看了几百小时还迷糊?关注我,几分钟让你秒懂!
在高并发、大数据量的业务场景中,单库单表很容易成为性能瓶颈。为了解决这个问题,“分库分表”成了后端开发绕不开的话题。而Sharding-JDBC(现更名为ShardingSphere-JDBC)作为 Apache 顶级项目,是 Java 生态中最主流的分库分表中间件之一。
本文将带你从零基础到实战落地,使用Java + Spring Boot搭建一个完整的分库分表案例,并讲解为什么用、怎么用、反例避坑,让小白也能一目了然!
一、需求场景:为什么要分库分表?
📌 场景描述
假设你正在开发一个电商平台的订单系统:
- 日均订单量:50万+
- 单表数据量:超过 2000 万行
- 查询变慢、写入延迟、备份困难、主从同步压力大……
这时候,单库单表已经扛不住了!
✅ 解决方案:分库分表
- 分库(Database Sharding):把数据分散到多个数据库实例,减轻单库压力。
- 分表(Table Sharding):把一张大表拆成多张小表,提升查询效率。
举个例子:1亿条订单数据 → 拆成 2 个库 × 4 张表 = 8 份,每份仅 1250 万条。
二、Sharding-JDBC 是什么?
Sharding-JDBC 是客户端直连数据库的轻量级 Java 框架,以JDBC 驱动的形式嵌入应用,对业务代码几乎无侵入。
- ✅ 优点:轻量、高性能、无需额外部署中间件
- ❌ 缺点:不支持跨库复杂 JOIN、事务需谨慎处理
它属于ShardingSphere生态的一部分,还有 Sharding-Proxy(代理模式)等,但 JDBC 模式最适合 Spring Boot 应用。
三、实战:Spring Boot + Sharding-JDBC 分库分表
1️⃣ 环境准备
- JDK 17
- Spring Boot 3.2.x
- MySQL 8.0(本地或 Docker)
- Maven
2️⃣ 数据库设计(模拟 2 库 4 表)
-- 创建两个数据库 CREATE DATABASE order_db_0; CREATE DATABASE order_db_1; -- 在每个库中创建两张订单表 USE order_db_0; CREATE TABLE t_order_0 (order_id BIGINT PRIMARY KEY, user_id BIGINT, amount DECIMAL(10,2)); CREATE TABLE t_order_1 (order_id BIGINT PRIMARY KEY, user_id BIGINT, amount DECIMAL(10,2)); USE order_db_1; CREATE TABLE t_order_0 (order_id BIGINT PRIMARY KEY, user_id BIGINT, amount DECIMAL(10,2)); CREATE TABLE t_order_1 (order_id BIGINT PRIMARY KEY, user_id BIGINT, amount DECIMAL(10,2));分片规则:
- 库分片键:
user_id % 2→ 决定进order_db_0还是order_db_1- 表分片键:
order_id % 2→ 决定进t_order_0还是t_order_1
3️⃣ Spring Boot 项目搭建
pom.xml 依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.4.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies>注意:ShardingSphere 5.x 起,包名从
io.shardingsphere改为org.apache.shardingsphere
application.yml 配置(核心!)
spring: shardingsphere: # 数据源配置 datasource: names: ds0,ds1 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/order_db_0?useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/order_db_1?useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password # 分片规则 rules: sharding: tables: t_order: actual-data-nodes: ds$->{0..1}.t_order_$->{0..1} table-strategy: standard: sharding-column: order_id sharding-algorithm-name: table-inline database-strategy: standard: sharding-column: user_id sharding-algorithm-name: db-inline sharding-algorithms: db-inline: type: INLINE props: algorithm-expression: ds$->{user_id % 2} table-inline: type: INLINE props: algorithm-expression: t_order_$->{order_id % 2} # 打印 SQL(调试用) props: sql-show: true4️⃣ 实体类与 Mapper
// Order.java public class Order { private Long orderId; private Long userId; private BigDecimal amount; // getter/setter... }// OrderMapper.java(使用 MyBatis 或 JPA 均可,这里简化用 JdbcTemplate) @Repository public class OrderMapper { @Autowired private JdbcTemplate jdbcTemplate; public void insert(Order order) { String sql = "INSERT INTO t_order (order_id, user_id, amount) VALUES (?, ?, ?)"; jdbcTemplate.update(sql, order.getOrderId(), order.getUserId(), order.getAmount()); } public List<Order> findByUserId(Long userId) { String sql = "SELECT * FROM t_order WHERE user_id = ?"; return jdbcTemplate.query(sql, (rs, rowNum) -> new Order( rs.getLong("order_id"), rs.getLong("user_id"), rs.getBigDecimal("amount") ), userId); } }注意:SQL 中写的是逻辑表名
t_order,Sharding-JDBC 会自动路由到真实表。
5️⃣ 测试 Controller
@RestController public class OrderController { @Autowired private OrderMapper orderMapper; @PostMapping("/order") public String createOrder(@RequestBody Order order) { orderMapper.insert(order); return "Order inserted!"; } @GetMapping("/orders/{userId}") public List<Order> getOrders(@PathVariable Long userId) { return orderMapper.findByUserId(userId); } }6️⃣ 测试效果
插入
userId=1001, orderId=10001
→user_id % 2 = 1→ 库ds1
→order_id % 2 = 1→ 表t_order_1
✅ 数据写入order_db_1.t_order_1查询
userId=1001
→ 自动路由到ds1,并扫描t_order_0和t_order_1(因为不知道 order_id)
✅ 返回所有该用户订单
控制台会打印实际执行的 SQL,验证是否命中正确库表。
四、反例 & 常见错误(避坑指南)
❌ 反例1:不分片键直接查全表
// 错误!没有分片键,会广播查询所有库所有表! List<Order> all = jdbcTemplate.query("SELECT * FROM t_order", ...);后果:8 个表全查,性能爆炸!
✅ 正确做法:尽量带分片键(如user_id)查询。
❌ 反例2:跨库事务不处理
@Transactional public void transfer(Order order1, Order order2) { orderMapper.insert(order1); // 可能写入 ds0 orderMapper.insert(order2); // 可能写入 ds1 }Sharding-JDBC 默认使用本地事务,跨库时无法保证 ACID!
✅ 解决方案:
- 使用Seata等分布式事务框架
- 或避免跨库写入(通过业务设计,如按 user_id 聚合)
❌ 反例3:ORDER BY / GROUP BY 跨分片性能差
SELECT user_id, SUM(amount) FROM t_order GROUP BY user_id;如果
user_id不是分片键,会从所有分片拉数据到内存聚合,OOM 风险!
✅ 建议:聚合操作尽量在单分片内完成,或使用异步数仓。
五、注意事项总结
| 项目 | 说明 |
|---|---|
| 分片键选择 | 必须高频查询字段,如 user_id、tenant_id |
| 避免全表扫描 | 不带分片键的查询慎用 |
| 事务范围 | 跨库事务需额外处理 |
| JOIN 限制 | 仅支持同一分片内的表 JOIN |
| 自增 ID | 建议用雪花算法(ShardingSphere 提供SNOWFLAKE算法) |
| 版本兼容 | Spring Boot 3 + JDK 17 请用 ShardingSphere 5.3+ |
六、结语
分库分表不是银弹,但在数据量爆炸时是必经之路。Sharding-JDBC 以其轻量、易集成、高性能的特点,成为 Spring Boot 项目的首选方案。
只要掌握分片规则设计 + 避开跨库陷阱,你就能轻松驾驭千万级数据!
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!