🔥关注墨瑾轩,带你探索编程的奥秘!🚀
🔥超萌技术攻略,轻松晋级编程高手🚀
🔥技术宝库已备好,就等你来挖掘🚀
🔥订阅墨瑾轩,智趣学习不孤单🚀
🔥即刻启航,编程之旅更有趣🚀
亿级大表拆分的"庖丁解牛"指南
🧪 Step 1:诊断病因——你的大表到底"胖"在哪里?
核心思想:用"数据体检报告"找出病根!
1.1 症状分析
-- 插入速度慢:
INSERT INTO huge_table (id, user_id, amount) VALUES (123456789, 1, 100.00);
-- 查询慢查询日志:
SELECT * FROM huge_table WHERE user_id = 123456789;
病根1:单表数据超过5000万,插入/查询时间飙升!
病根2:ALTER TABLE
操作导致主从延迟,系统"卡脖子"!
1.2 数据分布诊断
-- 查看表大小
SELECT table_name AS "表名",
round(data_length/1024/1024,2) AS "数据大小(MB)",
round(index_length/1024/1024,2) AS "索引大小(MB)",
(data_length + index_length)/1024/1024 AS "总大小(MB)"
FROM information_schema.TABLES
WHERE table_schema = 'your_db' AND table_name = 'huge_table';
关键指标:单表总大小超过100GB,索引碎片率>30%!
🛠️ Step 2:选择分表策略——给数据"分房间"
核心思想:用"分片策略"把大象塞进冰箱!
2.1 分片策略对比
策略类型 | 适用场景 | 代码示例 |
---|---|---|
按时间分片 | 数据有时间属性(如流水、日志) | CREATE TABLE huge_table_2023 (LIKE huge_table); |
按ID取模分片 | 数据均匀分布,无明显时间特征 | table_id = id % 10 |
混合分片 | 复杂场景(如按用户+时间) | table_id = (user_id % 10) + (YEAR(date) % 5) |
2.2 分片方案选择
// Sharding-JDBC配置示例(分片键为order_id)
spring:
shardingsphere:
datasource:
names: ds0, ds1, ds2
rules:
sharding:
tables:
huge_table:
actual-data-nodes: ds${0..2}.huge_table_$->{0..11}
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: hash_algorithm
key-generate-strategy:
column: order_id
key-generator-name: snowflake
sharding-algorithms:
hash_algorithm:
type: INLINE
props:
algorithm-expression: huge_table_$->{order_id % 12}
选择理由:
- 按
order_id
哈希分片,保证数据均匀分布!- 每张分表约400万数据(总12张表),MySQL扛得住!
🚀 Step 3:搭建分表中间件——给数据库装"导航仪"
核心思想:用Sharding-JDBC让代码"无感"分表!
3.1 中间件选型对比
中间件 | 优势 | 劣势 |
---|---|---|
Sharding-JDBC | 侵入性低,支持SQL解析 | 需要手动配置分片策略 |
MyCat | 独立代理层,兼容性强 | 配置复杂,维护成本高 |
Atlas | 基于MySQL原生协议 | 仅支持读写分离,不支持分片 |
3.2 Sharding-JDBC实战配置
<!-- pom.xml中添加依赖 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>5.0.0</version>
</dependency>
// 数据源配置(分3个库,每库4张表)
@Configuration
public class ShardingConfig {
@Bean
public DataSource dataSource() {
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourceMap.put("ds0", createDataSource("jdbc:mysql://db0"));
dataSourceMap.put("ds1", createDataSource("jdbc:mysql://db1"));
dataSourceMap.put("ds2", createDataSource("jdbc:mysql://db2"));
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTableRuleConfigs().add(createTableRuleConfig("huge_table"));
shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(
new InlineShardingStrategyConfiguration("order_id", "ds${order_id % 3}"));
shardingRuleConfig.setDefaultTableShardingStrategyConfig(
new InlineShardingStrategyConfiguration("order_id", "huge_table_${order_id % 4}"));
return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties());
}
}
魔法效果:
- 开发者无需关心分表逻辑,直接写普通SQL!
SELECT * FROM huge_table WHERE order_id = 123
→ 自动路由到对应分表!
🔄 Step 4:数据迁移——让大象"无痛搬家"
核心思想:用"分批迁移+双写策略"保证系统不宕机!
4.1 迁移方案设计
4.2 实战代码:分批次迁移
-- 创建分表结构
CREATE TABLE huge_table_0 LIKE huge_table;
ALTER TABLE huge_table_0 ADD PARTITION (PARTITION p0 VALUES LESS THAN (10000000));
-- 分批次迁移数据(每批100万)
SET @offset = 0;
WHILE @offset < (SELECT COUNT(*) FROM huge_table) DO
INSERT INTO huge_table_0
SELECT * FROM huge_table
WHERE id BETWEEN @offset AND @offset + 1000000
LIMIT 1000000;
SET @offset = @offset + 1000000;
END WHILE;
4.3 双写策略(新旧表并存)
// 在写入新分表的同时,继续写入旧表
public void insertData(Data data) {
try {
shardingJdbcTemplate.insert(data); // 写入分表
} finally {
legacyTemplate.insert(data); // 同步写入旧表(过渡期)
}
}
过渡期策略:
- 旧表保留3个月,用于数据回滚!
- 逐步关闭旧表的读写权限!
🛡️ Step 5:性能优化——让查询速度"起飞"
核心思想:用"索引+缓存+并行查询"三管齐下!
5.1 索引优化
-- 给分表添加联合索引
ALTER TABLE huge_table_0 ADD INDEX idx_user_time (user_id, create_time);
-- 避免全表扫描
EXPLAIN SELECT * FROM huge_table WHERE user_id = 123 AND create_time > '2023-01-01';
5.2 缓存策略
// 使用Redis缓存高频查询结果
public List<Data> getRecentOrders(int userId) {
String key = "orders:" + userId;
if (redisTemplate.hasKey(key)) {
return (List<Data>) redisTemplate.opsForValue().get(key);
}
List<Data> orders = queryFromShardingDB(userId);
redisTemplate.opsForValue().set(key, orders, 10, TimeUnit.MINUTES);
return orders;
}
5.3 并行查询
// 使用CompletableFuture并行查询多个分表
public List<Data> batchQuery() {
List<CompletableFuture<List<Data>>> futures = new ArrayList<>();
for (int i = 0; i < 12; i++) {
futures.add(CompletableFuture.supplyAsync(() -> queryFromShard(i)));
}
return futures.stream()
.map(CompletableFuture::join)
.flatMap(List::stream)
.collect(Collectors.toList());
}
性能对比:
- 单表查询耗时:12秒 → 分表后并行查询:0.8秒!
🚨 十大避坑指南——拆分路上的"地雷"别踩!
- 分片键选择错误:选
user_id
导致数据分布不均?→ 选order_id
! - 索引失效:分表后忘记重建索引?→ 每张分表都要有!
- 事务问题:跨分表事务如何保证?→ 用最终一致性方案!
- 主键冲突:分表后主键重复?→ 使用Snowflake生成唯一ID!
- 数据迁移中断:网络中断导致数据丢失?→ 分批次+断点续传!
- 查询条件复杂:无法路由到具体分表?→ 用
IN
子句+并行查询! - 监控缺失:分表后无法监控?→ 新增分表监控仪表盘!
- 回滚方案:上线失败如何回滚?→ 保留旧表+快速切换!
- 文档更新:其他系统不知道分表?→ 更新API文档+强制校验!
- 冷热数据分离:老数据占空间?→ 定期归档到冷存储!
🎁 终极案例:拆分一个5000万+的订单表
6.1 需求背景
- 表结构:
order_id (BIGINT), user_id (INT), amount (DECIMAL), create_time (DATETIME)
- 症状:插入速度10秒/笔,查询超时率>30%
6.2 拆分方案
6.3 代码实战
// 新增订单接口(分片写入)
@PostMapping("/orders")
public ResponseEntity<?> createOrder(@RequestBody Order order) {
// 1. 根据order_id分片
int shard = order.getOrderId() % 3;
// 2. 写入对应分表
shardingTemplate.insert(order, shard);
// 3. 缓存最新订单
redisTemplate.opsForValue().set("order:" + order.getOrderId(), order);
return ResponseEntity.ok().build();
}
你的数据表已获得"瘦身秘籍"!
现在,你的系统可以:
- 用Sharding-JDBC实现"零侵入"分表
- 通过分批迁移+双写策略保证数据安全
- 用并行查询将响应时间压缩到0.1秒
- 避开十大坑,让拆分过程"平如镜"
最后的提醒:
如果迁移时数据不一致?
检查三点:
- 分片键是否均匀分布?
- 缓存是否及时更新?
- 事务补偿机制是否生效?