千万级大表DELETE实战:业务卡死2小时后的血泪教训与优化方案
|
admin
2025年12月29日 22:5
本文热度 404
|
一、真实场景复现:一次DELETE引发的“业务雪崩”背景:某电商平台需清理1000万条3年前的历史订单数据(表总数据量5000万+),开发直接执行
DELETE FROM order_info WHERE create_time < '2022-01-01'
结果:
业务卡死:DELETE逐行删除锁表2小时,用户无法下单,客服投诉量激增;
磁盘爆炸:undo log占满2TB磁盘,数据库宕机;
主从延迟:从库延迟从1秒飙升至3小时,财务结算中断。
核心问题:
锁表风险:DELETE逐行删除全程持锁,大事务导致业务不可用;
资源消耗:千万级DELETE产生巨量redo/undo log,CPU和I/O压力骤增;
无进度反馈:业务方无法感知删除进度,陷入“盲等焦虑”。
二、Java工程师必看:3种DELETE优化方案(附生产级代码)
方案1:LIMIT分批删除(推荐!)
适用场景:需保留部分数据(如删除历史订单)。
优势:
零锁表:小批量提交,事务短,锁持有时间极短;
可控性强:可动态调整批次大小,平衡性能与进度。
实现代码(MyBatis + PageHelper):
// 1. 分页删除(每批1万条)
public void batchDelete() {
int batchSize = 10000;
int totalDeleted = 0;
long startTime = System.currentTimeMillis();
do {
// 分页查询待删除数据(按主键范围分页,避免全表扫描)
PageHelper.startPage(1, batchSize);
List<Order> orders = orderMapper.selectByCreateTimeBefore("2022-01-01");
if (orders.isEmpty()) break;
// 批量删除(使用MyBatis批量操作)
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
OrderMapper batchMapper = sqlSession.getMapper(OrderMapper.class);
for (Order order : orders) {
batchMapper.deleteById(order.getId());
}
sqlSession.commit();
sqlSession.close();
totalDeleted += orders.size();
log.info("已删除 {} 条,耗时 {} 秒", totalDeleted, (System.currentTimeMillis()-startTime)/1000);
Thread.sleep(500); // 降低数据库压力
} while (true);
}
优化技巧:
方案2:表分区(极速删除)
适用场景:数据按时间/地域等维度自然分区(如日志表)。
优势:
实现代码(Spring Boot + MyBatis):
// 1. 创建分区表(按月份分区)
@Update("CREATE TABLE order_2024 (id INT PRIMARY KEY, ...) " +
"PARTITION BY RANGE (YEAR(create_time)) (" +
"PARTITION p202401 VALUES LESS THAN (202402))")
void createPartitionedTable();
// 2. 删除旧分区(业务低峰期执行)
@Update("ALTER TABLE order DROP PARTITION p202312")
void dropPartition();
方案3:临时表重建(数据保留场景)
适用场景:需保留部分数据(如保留最近3个月)。
优势:
实现代码(Spring Batch):
// 1. 创建临时表并插入保留数据
@Update("CREATE TABLE order_temp LIKE order")
void createTempTable();
@Update("INSERT INTO order_temp SELECT * FROM order WHERE create_time >= '2022-01-01'")
void copyData();
// 2. 原子切换表名
@Update("RENAME TABLE order TO order_old, order_temp TO order")
void switchTables();
// 3. 异步清理旧表(后台线程执行)
@Async
public void dropOldTable() {
try {
Thread.sleep(60000); // 等待业务切换完成
orderMapper.dropTable("order_old");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
三、避坑指南:Java工程师的DELETE血泪经验
绝对不要做:
必须监控:
性能调优参数:
# 降低DELETE对业务影响
innodb_flush_log_at_trx_commit=2 # 牺牲部分数据安全换取速度
innodb_io_capacity=2000 # 提升磁盘IO吞吐
阅读原文:原文链接
该文章在 2025/12/31 10:26:23 编辑过