做后端开发的同学,多少都跟定时清理数据的需求打过交道。最近我们团队就因为一句看似简单的“保留最近1个月数据”,踩了个差点把业务拖垮的大坑——原本设计每小时只删1小时的历史数据,结果到了月份切换的临界点,一次性删了25小时的数据,直接把数据库CPU干到100%,业务接口超时告警响个不停。今天就把这个踩坑复盘分享出来,希望能帮大家避坑。
一、需求与初始设计:看似完美的定时清理方案
我们的业务是实时数据统计平台,数据生成速度极快,每天新增数据量超过1000万条。为了控制数据库存储成本,产品提出需求:只保留最近1个月的数据,超过1个月的历史数据定时清理。
研发同学最初的设计思路很直接:
- 用定时任务框架(Quartz)配置一个每小时执行一次的任务;
- 每次执行时,删除“当前时间减去1个月”之前的所有数据;
- SQL条件大致是:
DELETE FROM data_table WHERE create_time < DATE_SUB(NOW(), INTERVAL 1 MONTH)。
这个方案看起来逻辑通顺:每小时删一次,每次只删过去1小时产生的“超期数据”,既能保证数据及时清理,又不会给数据库造成太大压力。上线前的测试也一切正常,每次任务执行都只删除几万条数据,耗时几秒就完成了。
二、突发故障:临界点的“数据雪崩”
上线后的前20多天,系统运行平稳,定时任务按预期执行。直到上个月月底(4月30日)到5月1日的凌晨,监控突然发出紧急告警:
- 数据库CPU使用率飙升至100%,持续超过30分钟;
- 业务接口平均响应时间从200ms暴涨到5s以上,大量请求超时;
- 查看定时任务日志发现,原本只需要几秒的清理任务,这次执行了近20分钟,删除的数据量高达2500万条——相当于平时25次任务的删除量总和。
紧急排查后我们发现,问题就出在“1个月”这个时间定义上。
三、根因分析:“一个月”不是固定时长
我们的定时任务用了DATE_SUB(NOW(), INTERVAL 1 MONTH)来计算数据保留的截止时间,这个函数在不同日期执行时,结果的时间跨度是不一样的:
- 当在3月31日执行时,
INTERVAL 1 MONTH会回到2月28日(非闰年),时间跨度是31天; - 当在4月30日执行时,
INTERVAL 1 MONTH会回到3月30日,时间跨度是30天; - 当在5月1日执行时,
INTERVAL 1 MONTH会回到4月1日,时间跨度是30天。
而我们的定时任务是每小时执行一次,这就导致了:
- 4月30日23点执行时,删除的是“3月30日23点之前”的数据;
- 5月1日0点执行时,删除的是“4月1日0点之前”的数据。
这两个时间点之间,整整差了25个小时(从3月30日23点到4月1日0点)。原本应该在4月30日0点到23点分23次删除的数据,全部堆积到5月1日0点的这一次任务中执行,瞬间形成了“数据雪崩”。
大量的删除操作不仅占用了数据库的CPU和IO资源,还因为长时间持有表锁,导致业务的读写请求被阻塞,最终引发了业务故障。
四、修复方案:用固定时长替代相对月份
找到问题根源后,我们立刻对定时任务进行了修复:
- 将“1个月”的相对时间改为固定的30天:把删除条件从
INTERVAL 1 MONTH改为INTERVAL 30 DAY,确保每次计算的截止时间都是当前时间往前推30天,时间跨度固定; - 优化删除逻辑,避免大事务:即使是固定30天,也可能因为数据量过大导致单次删除太多。我们新增了分批删除逻辑,每次只删除10万条数据,循环执行直到所有超期数据清理完成;
- 增加监控告警:给定时任务的执行时长、删除数据量设置阈值告警,一旦出现异常立刻通知运维人员。
修复后的SQL大致如下:
-- 分批删除30天前的数据,每次删10万条 DELETE FROM data_table ``WHERE create_time < DATE_SUB(NOW(), INTERVAL 30 DAY) LIMIT 100000;
同时,我们还对数据库进行了优化:给create_time字段建立了分区,后续可以直接通过删除分区来清理数据,效率比逐行删除高得多。
五、总结与反思:那些容易被忽略的时间细节
这次故障给我们团队带来了深刻的教训,看似简单的时间处理,背后藏着不少容易踩的坑:
- 慎用相对时间单位:在涉及定时任务、数据清理等场景时,尽量用
DAY、HOUR等固定时长单位,避免使用MONTH、YEAR这类相对单位,因为它们的时间跨度不固定; - 边界场景一定要测试:上线前不仅要测试正常场景,还要重点测试月末、季末、年末等时间临界点,以及闰年、闰月等特殊日期;
- 大表操作要分批:对大数据量的表进行删除、更新操作时,一定要分批执行,避免单次操作数据量过大导致数据库压力激增;
- 监控告警要全面:除了监控业务指标,还要对定时任务的执行情况、数据库的资源使用情况进行监控,做到故障早发现、早处理。
技术开发没有小事,任何一个看似不起眼的细节,都可能引发严重的生产事故。希望我们这次踩的坑,能给大家提个醒,在处理时间相关的逻辑时多留个心眼。