Spring JPA deleteInBatch导致StackOverflow问题
作者:Mr-Wanter
这篇文章主要介绍了Spring JPA deleteInBatch导致StackOverflow问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
数据库日志库定时清理日志数据,几天之后发现,jvm内存溢出现象。
一、问题定位
1、日志数据量太大(近40w数据)
2、源码分析
@Scheduled(cron = "0 0 1 * * ?") public void timedPullNewInfo() { Date date=new Date(); Calendar ca=Calendar.getInstance(); try{ ca.setTime(date); ca.add(ca.DATE, -30); Date queryDate = ca.getTime(); log.info("进入定时任务的方法中来清理前30天的操作日志================"); List<GempUserOperationLogPO> polist = gempUserOperationLogDao.findByCreateTimeBefore(queryDate); gempUserOperationLogDao.deleteInBatch(polist); }catch (Exception e){ log.info("清理前30天的操作日志出错================"+e.getMessage()); } }
3、问题定位在
gempUserOperationLogDao.deleteInBatch(polist);
二、内存溢出原理
jpa封装的deleteInBatch底层执行逻辑为
delete from [table_name] where [criteria] = id or [criteria] = id (and so on...)
HqlSqlBaseWalker需要搜索遍历所有的where条件语句,当数据量过大时就会导致内存溢出。
三、其他尝试
1、循环遍历删除delete
List<GempUserOperationLogPO> polist = gempUserOperationLogDao.findByCreateTimeBefore(queryDate); polist.forEach(x->{ gempUserOperationLogDao.delete(x); });
四十万数据等不起…
2、分批次删除
//按每1000个一组分割 private static final Integer MAX_NUMBER = 1000; /** * 计算切分次数 */ private static Integer countStep(Integer size) { return (size + MAX_NUMBER - 1) / MAX_NUMBER; } @Scheduled(cron = "0 0 1 * * ?") public void timedPullNewInfo() { Date date=new Date(); Calendar ca=Calendar.getInstance(); try{ ca.setTime(date); ca.add(ca.DATE, -30); Date queryDate = ca.getTime(); log.info("进入定时任务的方法中来清理前30天的操作日志================"); List<GempUserOperationLogPO> polist = gempUserOperationLogDao.findByCreateTimeBefore(queryDate); int limit = countStep(polist.size()); List<List<GempUserOperationLogPO>> mglist = new ArrayList<>(); Stream.iterate(0, n -> n + 1).limit(limit).forEach(i -> { mglist.add(polist.stream().skip(i * MAX_NUMBER).limit(MAX_NUMBER).collect(Collectors.toList())); }); mglist.forEach(x->{ gempUserOperationLogDao.deleteInBatch(x); }); }catch (Exception e){ log.info("清理前30天的操作日志出错================"+e.getMessage()); } }
还是等不起…
3、直接全部删除
gempUserOperationLogDao.deleteAll();
deleteAll()底层执行逻辑:
- 1、查询所有数据
- 2、根据id逐一删除
与方式一没有区别,等到天荒地老
四、解决方案
原生sql删除,秒级方案
public interface GempUserOperationLogDao extends JpaRepository<GempUserOperationLogPO, String>, JpaSpecificationExecutor<GempUserOperationLogPO> { @Transactional @Modifying @Query(value = "delete from user_operation_log where create_time < ?1", nativeQuery = true) int deleteAllLists(Date date); }
StopWatch sw = new StopWatch(); sw.start("校验耗时"); gempUserOperationLogDao.deleteAllLists(queryDate); sw.stop(); System.out.println(sw.prettyPrint());
StopWatch '': running time (millis) = 943 | ||
---|---|---|
ms | % | Task name |
00943 | 100% | 校验耗时 |
总结
面对大数据量的数据批量修改、删除操作时,不要使用jpa封装的方法操作数据,编写原生sql效率更高且不易发生问题。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。