java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring JPA deleteInBatch导致StackOverflow

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()底层执行逻辑:

与方式一没有区别,等到天荒地老

四、解决方案

原生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
00943100%校验耗时

总结

面对大数据量的数据批量修改、删除操作时,不要使用jpa封装的方法操作数据,编写原生sql效率更高且不易发生问题。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文