Spring?JPA?deleteInBatch導致StackOverflow問題
更新時間:2024年05月07日 09:01:01 作者:Mr-Wanter
這篇文章主要介紹了Spring?JPA?deleteInBatch導致StackOverflow問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
數據庫日志庫定時清理日志數據,幾天之后發(fā)現,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底層執(zhí)行邏輯為
delete from [table_name] where [criteria] = id or [criteria] = id (and so on...)
HqlSqlBaseWalker需要搜索遍歷所有的where條件語句,當數據量過大時就會導致內存溢出。
三、其他嘗試
1、循環(huán)遍歷刪除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()底層執(zhí)行邏輯:
- 1、查詢所有數據
- 2、根據id逐一刪除
與方式一沒有區(qū)別,等到天荒地老
四、解決方案
原生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效率更高且不易發(fā)生問題。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

