1. 项目概述:为什么需要分库分表?
在互联网应用快速发展的今天,数据量呈现爆炸式增长。我经历过一个电商项目,仅仅一年时间订单表就突破了5000万条记录,单表查询性能从最初的毫秒级响应逐渐恶化到秒级。这就是典型的需要考虑分库分表的场景。
分库分表本质上是通过水平拆分(Horizontal Partitioning)将数据分散到不同的数据库实例或表中,从而突破单机存储和性能瓶颈。Sharding-JDBC作为轻量级的Java框架,完美解决了传统分库分表方案中应用层需要处理复杂路由逻辑的问题。它通过JDBC层透明化数据分片,让开发者可以像操作单库单表一样操作分片后的数据。
提示:当单表数据量超过500万,或预计一年内会超过这个阈值时,就应该开始考虑分库分表方案了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Sharding-JDBC核心架构解析
2.1 分片策略设计要点
Sharding-JDBC的核心在于分片策略配置。我在实际项目中常用的策略组合是:
- 精确分片(PreciseShardingAlgorithm):适用于等值查询条件
java复制public class OrderDatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
// 按订单ID取模分库
long orderId = shardingValue.getValue();
int dbIndex = (int) (orderId % availableTargetNames.size());
return "ds_" + dbIndex;
}
}
- 范围分片(RangeShardingAlgorithm):适合范围查询场景
java复制public class OrderTableRangeShardingAlgorithm implements RangeShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
RangeShardingValue<Long> shardingValue) {
// 按时间范围分表
Range<Long> range = shardingValue.getValueRange();
LocalDateTime lower = convertToDateTime(range.lowerEndpoint());
LocalDateTime upper = convertToDateTime(range.upperEndpoint());
// 计算需要查询的所有表名
return calculateTableNames(lower, upper);
}
}
2.2 分布式ID生成方案对比
分库分表后,自增ID会导致主键冲突。这是我在不同项目中验证过的几种方案:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Snowflake | 高性能,趋势递增 | 依赖系统时钟 | 高并发写入场景 |
| UUID | 简单易用 | 无序,影响索引效率 | 小规模应用 |
| 数据库序列 | 绝对有序 | 有性能瓶颈 | 传统企业应用 |
| Redis自增 | 性能较好 | 需要维护Redis高可用 | 已有Redis基础设施 |
实测下来,Snowflake在大多数场景下表现最优。以下是Spring Boot中的配置示例:
java复制@Bean
public IdGenerator idGenerator() {
return new SnowflakeIdGenerator(workerId, datacenterId);
}
3. Spring Boot整合实战
3.1 基础环境搭建
首先引入关键依赖(注意版本兼容性):
xml复制<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>5.1.1</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
3.2 完整配置示例
这是经过生产验证的配置模板:
yaml复制spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://db1:3306/order_db?useSSL=false
username: root
password: 123456
ds1:
type: com.zaxxer.hikari.HikariDataSource
# ...类似配置...
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order_$->{0..15}
database-strategy:
inline:
sharding-column: user_id
algorithm-expression: ds$->{user_id % 2}
table-strategy:
standard:
precise-algorithm-class-name: com.example.algorithm.OrderTablePreciseShardingAlgorithm
range-algorithm-class-name: com.example.algorithm.OrderTableRangeShardingAlgorithm
key-generator:
column: order_id
type: SNOWFLAKE
注意:生产环境一定要配置Hikari连接池参数,特别是maxPoolSize和idleTimeout,否则高并发下会出现连接泄漏。
4. 高级特性与性能优化
4.1 读写分离配置
结合分库分表实现读写分离:
yaml复制spring:
shardingsphere:
masterslave:
name: ms_ds
master-data-source-name: ds_master
slave-data-source-names: ds_slave0,ds_slave1
load-balance-algorithm-type: ROUND_ROBIN
4.2 分布式事务处理
Sharding-JDBC支持多种事务类型:
- 本地事务:默认模式,单库ACID
- XA事务:强一致性,性能较差
java复制@ShardingTransactionType(TransactionType.XA)
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 跨库操作
}
- BASE事务:推荐使用Seata实现
yaml复制spring:
shardingsphere:
props:
sql.show: true
max.connections.size.per.query: 5
executor.size: 20
seata.enable: true
5. 生产环境踩坑实录
5.1 分页查询优化
错误做法:
sql复制SELECT * FROM t_order WHERE user_id=123 ORDER BY create_time DESC LIMIT 10000, 10
正确方案:
java复制// 先获取满足条件的记录ID
List<Long> orderIds = orderMapper.findIdsByUser(userId, pageable);
// 再通过ID精确查询
return orderMapper.findByIds(orderIds);
5.2 热点数据问题
我们曾遇到大促期间某个分片负载飙升的情况。解决方案:
- 增加分片数量(从16个扩展到64个)
- 采用复合分片键(user_id + order_type)
- 引入缓存层缓解数据库压力
5.3 监控与调优
关键指标监控项:
- 分片执行耗时:spring.shardingsphere.props.sql.show=true
- 连接池状态:/actuator/hikari
- 慢查询日志:设置maxQueryTime
JVM参数建议:
code复制-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=35
6. 完整项目代码结构
推荐的项目组织结构:
code复制src/main/java
├── config
│ ├── ShardingConfig.java # 分片规则配置
│ └── TransactionConfig.java # 事务配置
├── algorithm # 分片算法
│ ├── OrderDatabaseShardingAlgorithm.java
│ └── OrderTableShardingAlgorithm.java
├── entity # 实体类
│ └── Order.java
├── repository # 数据访问层
│ └── OrderRepository.java
└── service # 业务逻辑
└── OrderService.java
关键实体类示例:
java复制@Data
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(generator = "snowflake")
private Long orderId;
@Column(name = "user_id")
private Long userId;
@Column(name = "order_amount")
private BigDecimal amount;
@Column(name = "create_time")
private LocalDateTime createTime;
}
我在实际开发中发现,使用MyBatis-Plus配合Sharding-JDBC时,需要特别注意Wrapper条件构造。避免使用未包含分片键的查询条件,否则会导致全库全表路由,性能急剧下降。
