系列导读:本篇将深入讲解分库分表的核心原理、设计策略与最佳实践。
文章目录
-
- 一、分库分表概述
-
- [1.1 为什么需要分库分表?](#1.1 为什么需要分库分表?)
- [1.2 分库 vs 分表](#1.2 分库 vs 分表)
- [1.3 架构图](#1.3 架构图)
- 二、分片策略
-
- [2.1 分片键选择](#2.1 分片键选择)
- [2.2 分片算法](#2.2 分片算法)
- [2.3 分片配置](#2.3 分片配置)
- 三、主键设计
-
- [3.1 主键问题](#3.1 主键问题)
- [3.2 雪花算法](#3.2 雪花算法)
- 四、中间件选型
-
- [4.1 中间件对比](#4.1 中间件对比)
- [4.2 ShardingSphere 优势](#4.2 ShardingSphere 优势)
- 五、实战案例
-
- [5.1 订单分库分表](#5.1 订单分库分表)
- [5.2 跨库查询](#5.2 跨库查询)
- 总结
一、分库分表概述
1.1 为什么需要分库分表?
| 问题 | 说明 |
|---|---|
| 数据量过大 | 单表超过千万级,查询变慢 |
| 并发量过大 | 单库连接数有限 |
| 单点故障 | 单库宕机影响全局 |
1.2 分库 vs 分表
| 方式 | 说明 | 解决问题 |
|---|---|---|
| 垂直分库 | 按业务拆分数据库 | 业务解耦、独立扩展 |
| 垂直分表 | 按字段拆分表 | 减少IO、提高性能 |
| 水平分库 | 按数据拆分到多个库 | 解决高并发、大数据量 |
| 水平分表 | 按数据拆分到多个表 | 解决单表数据量过大 |
1.3 架构图
垂直分库:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 用户库 │ │ 订单库 │ │ 商品库 │
└─────────┘ └─────────┘ └─────────┘
水平分库:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 订单库0 │ │ 订单库1 │ │ 订单库2 │
│ order_0 │ │ order_1 │ │ order_2 │
│ order_1 │ │ order_2 │ │ order_0 │
└─────────┘ └─────────┘ └─────────┘
二、分片策略
2.1 分片键选择
分片键选择原则:
1. 数据分布均匀
2. 查询命中率高
3. 业务关联性强
常见分片键:
- 用户ID:用户相关业务
- 订单ID:订单相关业务
- 时间:日志、流水类业务
2.2 分片算法
| 算法 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| Hash | hash(key) % N | 分布均匀 | 扩容困难 |
| Range | 按范围划分 | 扩容简单 | 热点问题 |
| 一致性Hash | 环形Hash | 扩容影响小 | 实现复杂 |
| 取模 | key % N | 简单 | 扩容困难 |
2.3 分片配置
yaml
# ShardingSphere 配置
spring:
shardingsphere:
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:
type: INLINE
props:
algorithm-expression: t_order_$->{order_id % 2}
key-generators:
snowflake:
type: SNOWFLAKE
三、主键设计
3.1 主键问题
单库自增ID问题:
- 分库后ID冲突
- 无法保证全局唯一
解决方案:
- UUID:无序、太长
- 雪花算法:推荐
- 号段模式:数据库批量获取
3.2 雪花算法
java
// 雪花算法生成分布式ID
public class SnowflakeIdWorker {
private final long workerId;
private final long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & 4095;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - 1288834974657L) << 22)
| (datacenterId << 17)
| (workerId << 12)
| sequence;
}
}
四、中间件选型
4.1 中间件对比
| 中间件 | 类型 | 优点 | 缺点 |
|---|---|---|---|
| ShardingSphere | 客户端 | 轻量、功能全 | 侵入应用 |
| MyCat | 服务端 | 对应用透明 | 性能有损耗 |
| Vitess | 服务端 | 大厂背书 | 学习成本高 |
4.2 ShardingSphere 优势
✅ 功能丰富:分片、读写分离、加密、影子库
✅ 生态完善:支持 Spring Boot、Namespace
✅ 性能优秀:客户端直连数据库
✅ 社区活跃:Apache 顶级项目
五、实战案例
5.1 订单分库分表
java
// 订单实体
@Data
@TableName("t_order")
public class Order {
@TableId(type = IdType.ASSIGN_ID) // 雪花算法
private Long orderId;
private Long userId;
private String orderNo;
private BigDecimal amount;
private Integer status;
private LocalDateTime createTime;
}
// 分片配置
@Configuration
public class ShardingConfig {
@Bean
public DataSource dataSource() throws SQLException {
// 数据源配置
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourceMap.put("ds0", createDataSource("192.168.1.100:3306/order_db0"));
dataSourceMap.put("ds1", createDataSource("192.168.1.101:3306/order_db1"));
// 分片规则
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTables().add(getOrderTableRuleConfiguration());
return ShardingDataSourceFactory.createDataSource(
dataSourceMap,
shardingRuleConfig,
new Properties()
);
}
private ShardingTableRuleConfiguration getOrderTableRuleConfiguration() {
ShardingTableRuleConfiguration result = new ShardingTableRuleConfiguration("t_order", "ds${0..1}.t_order_${0..1}");
result.setTableShardingStrategy(new StandardShardingStrategyConfiguration("order_id", "t_order_inline"));
return result;
}
}
5.2 跨库查询
java
// 跨库查询问题解决方案
@Service
public class OrderQueryService {
// 方案1:绑定表(关联分片键)
public List<Order> queryByUserId(Long userId) {
return orderMapper.selectByUserId(userId);
}
// 方案2:冗余数据
public List<Order> queryByOrderNo(String orderNo) {
// 从冗余表查询
return orderIndexMapper.selectByOrderNo(orderNo);
}
// 方案3:广播表
public Order queryWithRegion(Long orderId) {
Order order = orderMapper.selectById(orderId);
// 区域信息从广播表获取
Region region = regionMapper.selectById(order.getRegionId());
order.setRegionName(region.getName());
return order;
}
}
总结
✅ 分库分表概述 :为什么需要、分库vs分表
✅ 分片策略 :分片键、分片算法
✅ 主键设计 :雪花算法
✅ 中间件选型 :ShardingSphere、MyCat
✅ 实战案例:订单分库分表
下篇预告 :Redis 集群架构与优化
作者 :刘~浪地球
系列 :数据库与缓存(二)
更新时间:2026-04-11