一、为什么需要分库分表
当数据量超过单机数据库的承载能力时,分库分表成为必然选择:
- 单库数据量过亿:查询性能急剧下降
- 单表数据量过大:索引效率降低,DML操作变慢
- 连接数耗尽:数据库连接成为稀缺资源
- 存储空间不足:单机的磁盘容量有上限
二、分库分表策略
1. 垂直拆分
按业务将表或库拆分开:
原来:用户库(用户表、订单表、支付表)
拆分后:
用户库(用户表)
订单库(订单表)
支付库(支付表)
2. 水平拆分
按数据规则将表拆分:
sql
-- 按用户ID取模分表
users_0: WHERE user_id % 4 = 0
users_1: WHERE user_id % 4 = 1
users_2: WHERE user_id % 4 = 2
users_3: WHERE user_id % 4 = 3
3. 常见的分片键选择
| 分片键 | 适用场景 | 注意事项 |
|---|---|---|
| 用户ID | 电商、社交 | 查询需带用户ID |
| 时间 | 日志、订单 | 冷数据归档方便 |
| 地区 | 区域性业务 | 跨区查询困难 |
| 哈希 | 均衡分布 | 查询复杂度高 |
三、分库分表中间件
ShardingSphere
yaml
# application.yml
spring:
shardingsphere:
datasource:
ds0:
url: jdbc:mysql://localhost:3306/db0
driver-class-name: com.mysql.cj.jdbc.Driver
ds1:
url: jdbc:mysql://localhost:3306/db1
driver-class-name: com.mysql.cj.jdbc.Driver
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: order_inline
MyCat
xml
四、实战代码
Java SDK 使用示例
java
// ShardingSphere JDBC
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
Map dataSourceMap = new HashMap<>();
dataSourceMap.put("ds0", createDataSource("db0"));
dataSourceMap.put("ds1", createDataSource("db1"));
ShardingRuleConfiguration config = new ShardingRuleConfiguration();
config.getTables().add(createTableRule());
return DataSourceFactory.createDataSource(dataSourceMap, config);
}
}
// 分片键获取
public Long getShardingKey(Order order) {
return order.getUserId() % 4;
}
五、跨库查询解决方案
1. 禁止跨库JOIN
java
// 业务层面解决:先查用户,再查订单
User user = userMapper.selectById(userId);
List orders = orderMapper.selectByUserId(userId);
2. 异构表
sql
-- 在订单库冗余用户信息
CREATE TABLE t_order (
order_id BIGINT,
user_id BIGINT,
user_name VARCHAR(50), -- 冗余字段
amount DECIMAL(10,2),
created_at DATETIME
);
3. ES搜索引擎
MySQL(分库分表) → 数据同步 → Elasticsearch
↓
复杂查询由ES处理
六、分布式ID生成
分库分表后,需要分布式ID:
java
// Snowflake算法
public class SnowflakeIdGenerator {
private final long twepoch = 1288834974657L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
public synchronized long nextId() {
long timestamp = timeGen();
long id = ((timestamp - twepoch) << 22)
| (datacenterId << 17)
| (workerId << 12)
| sequence;
return id;
}
}
// 使用
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
long orderId = idGenerator.nextId();
七、常见问题与解决方案
Q1:扩容时如何迁移数据?
- 方案1:双写(新老库同时写)
- 方案2:定时任务迁移
- 方案3:使用一致性哈希减少迁移量
Q2:如何保证分页查询?
sql
-- 反例:深分页问题
SELECT * FROM t_order ORDER BY id LIMIT 1000000, 10
-- 正例:游标分页
SELECT * FROM t_order WHERE id > 1000000 ORDER BY id LIMIT 10
Q3:分布式事务如何处理?
使用 Seata AT 模式或 TCC 模式
八、总结
分库分表是系统扩展的必经之路,但增加了系统复杂度:
- ✅ 突破单机性能瓶颈
- ✅ 提高系统可用性
- ❌ 跨库查询困难
- ❌ 运维复杂度增加
思考题:你的系统中数据量最大的是哪张表?有没有考虑过分库分表?
个人观点,仅供参考