1. 为什么我们需要分库分表?
在互联网应用快速发展的今天,数据量呈现爆炸式增长。我清楚地记得去年接手的一个电商项目,用户表在短短半年内就从最初的几十万增长到了千万级别。当单表数据超过500万行时,即使有索引,查询性能也会明显下降。更糟糕的是,当数据量达到千万级,简单的分页查询都可能让数据库不堪重负。
1.1 单表瓶颈的典型表现
在实际项目中,单表瓶颈通常表现为以下几种情况:
- 查询响应时间从毫秒级骤增到秒级
- 数据库服务器CPU和内存使用率长期居高不下
- 简单的count操作需要数秒才能返回结果
- 高峰期经常出现连接池耗尽的情况
我曾经遇到过一个订单表,数据量达到800万时,即使最简单的根据用户ID查询订单列表的SQL,响应时间也从原来的20ms飙升到了800ms以上。这就是典型的单表瓶颈问题。
1.2 分库分表的本质
分库分表的核心思想是"分而治之"。通过将数据分散到不同的数据库或表中,每个库/表只处理部分数据,从而降低单个节点的压力。这就像把一个大仓库分成多个小仓库,每个仓库只存放特定类别的商品,找东西时就不用在整个大仓库里翻找了。
ShardingSphere作为Apache的顶级项目,提供了完善的分库分表解决方案。它最大的优势是几乎不需要修改业务代码,通过配置就能实现数据分片,这对已有系统的改造特别友好。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. SpringBoot3集成ShardingSphere前的准备
2.1 环境要求检查
在开始集成前,我们需要确保环境满足以下要求:
- JDK 17+(SpringBoot3的最低要求)
- SpringBoot 3.0.x
- ShardingSphere-JDBC 5.3.0+
- 数据库(MySQL 5.7+/PostgreSQL 10+)
注意:SpringBoot3对Java版本的要求较高,如果你的项目还在使用Java 8,需要先升级JDK版本。
2.2 Maven依赖配置
在pom.xml中添加以下依赖:
xml复制<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.3.0</version>
</dependency>
<!-- 根据实际使用的数据库添加驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
2.3 数据库准备
假设我们要对用户表(user)进行分库分表,需要先准备好物理库和表。这里我们采用2个库,每个库16张表的方案:
sql复制-- 在db0和db1中分别执行
CREATE TABLE user_0 (
id BIGINT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100),
create_time DATETIME,
-- 其他字段...
KEY idx_username (username)
);
-- 创建user_1到user_15...
3. ShardingSphere核心配置详解
3.1 基本分片配置
在application.yml中添加ShardingSphere配置:
yaml复制spring:
shardingsphere:
datasource:
names: db0,db1
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db0
username: root
password: password
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db1
username: root
password: password
rules:
sharding:
tables:
user:
actual-data-nodes: db$->{0..1}.user_$->{0..15}
database-strategy:
standard:
sharding-column: id
precise-algorithm-class-name: com.example.config.UserDatabaseShardingAlgorithm
table-strategy:
standard:
sharding-column: id
precise-algorithm-class-name: com.example.config.UserTableShardingAlgorithm
3.2 分片算法实现
创建自定义分片算法类:
java复制public class UserDatabaseShardingAlgorithm implements StandardShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
long id = shardingValue.getValue();
// 取模决定库
int dbIndex = (int) (id % 2);
for (String each : availableTargetNames) {
if (each.endsWith(String.valueOf(dbIndex))) {
return each;
}
}
throw new IllegalArgumentException();
}
// 其他必要方法...
}
public class UserTableShardingAlgorithm implements StandardShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
long id = shardingValue.getValue();
// 取模决定表
int tableIndex = (int) (id % 16);
return "user_" + tableIndex;
}
}
3.3 分布式ID生成配置
分库分表环境下,传统的自增ID不再适用。我们可以使用Snowflake算法:
yaml复制spring:
shardingsphere:
rules:
sharding:
key-generators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 123
然后在实体类中使用:
java复制@TableName("user")
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
// 其他字段...
}
4. 实战中的关键问题与解决方案
4.1 跨库查询问题
分库后,直接使用JOIN查询会变得困难。解决方案包括:
- 使用ShardingSphere的绑定表功能
- 在应用层做数据聚合
- 考虑使用冗余字段
例如,对于订单和用户信息关联查询,可以在订单表中冗余用户的基本信息:
java复制public class Order {
private Long id;
private Long userId;
// 冗余字段
private String userName;
private String userAvatar;
// 其他字段...
}
4.2 分布式事务处理
分库分表后,事务变成了分布式事务。ShardingSphere支持以下几种方案:
- XA事务(强一致性,性能较低)
- Seata AT模式(推荐)
- 最终一致性(如本地消息表)
配置Seata集成:
yaml复制spring:
shardingsphere:
rules:
sharding:
default-database-strategy:
none:
default-table-strategy:
none:
props:
sql-show: true
# 开启Seata集成
seata:
enable: true
application-id: your_app_name
tx-service-group: your_tx_group
4.3 数据迁移与扩容
当现有分片不够用时,需要考虑扩容。ShardingSphere提供了弹性伸缩功能:
- 准备新库新表
- 配置新分片规则
- 使用ShardingSphere-Scaling进行数据迁移
- 切换流量到新配置
bash复制# 启动迁移任务
bin/start.sh --config=config.yaml
5. 性能优化与监控
5.1 SQL优化建议
- 避免全表扫描:确保查询条件包含分片键
- 减少跨库查询:尽量在一个分片内完成操作
- 合理使用绑定表:关联查询的表使用相同的分片规则
5.2 监控配置
集成Prometheus监控:
yaml复制spring:
shardingsphere:
props:
metrics:
enabled: true
name: shardingsphere
prometheus:
enabled: true
port: 9090
props:
jvm-information:
enabled: true
5.3 常见问题排查
- 分片键选择不当:应该选择查询频繁且分布均匀的字段
- 热点数据问题:可以在分片算法中加入时间因子
- 连接池配置:适当增大连接池大小
yaml复制spring:
shardingsphere:
datasource:
db0:
hikari:
maximum-pool-size: 20
minimum-idle: 5
6. 实际项目中的经验分享
在最近的一个社交平台项目中,我们遇到了用户动态表快速增长的问题。最初的设计是单表存储所有用户动态,当数据量达到2000万时,查询性能急剧下降。我们采用了以下方案:
- 按用户ID分库(8个库)
- 按时间范围分表(每月一张表)
- 热点用户单独分片
这样设计后,即使是最活跃的用户,其数据也被分散到不同的表中。查询性能从原来的平均1.2秒降低到了80毫秒左右。
一个特别需要注意的点是:分片键一旦确定就很难修改。我们在初期选择了用户ID作为分片键,后来发现某些业务场景需要按时间范围查询,这就导致了跨分片查询的问题。最终我们通过在应用层缓存热点数据来解决这个问题。
另一个经验是:不要过度分片。我们曾经尝试过256个分片,结果发现维护成本极高,而且大多数分片的数据量都很小。最终调整为32个分片,在性能和可维护性之间取得了平衡。
