Spring?JPA?deleteInBatch導(dǎo)致StackOverflow問題
數(shù)據(jù)庫日志庫定時清理日志數(shù)據(jù),幾天之后發(fā)現(xiàn),jvm內(nèi)存溢出現(xiàn)象。
一、問題定位
1、日志數(shù)據(jù)量太大(近40w數(shù)據(jù))
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("進(jìn)入定時任務(wù)的方法中來清理前30天的操作日志================"); List<GempUserOperationLogPO> polist = gempUserOperationLogDao.findByCreateTimeBefore(queryDate); gempUserOperationLogDao.deleteInBatch(polist); }catch (Exception e){ log.info("清理前30天的操作日志出錯================"+e.getMessage()); } }
3、問題定位在
gempUserOperationLogDao.deleteInBatch(polist);
二、內(nèi)存溢出原理
jpa封裝的deleteInBatch底層執(zhí)行邏輯為
delete from [table_name] where [criteria] = id or [criteria] = id (and so on...)
HqlSqlBaseWalker需要搜索遍歷所有的where條件語句,當(dāng)數(shù)據(jù)量過大時就會導(dǎo)致內(nèi)存溢出。
三、其他嘗試
1、循環(huán)遍歷刪除delete
List<GempUserOperationLogPO> polist = gempUserOperationLogDao.findByCreateTimeBefore(queryDate); polist.forEach(x->{ gempUserOperationLogDao.delete(x); });
四十萬數(shù)據(jù)等不起…
2、分批次刪除
//按每1000個一組分割 private static final Integer MAX_NUMBER = 1000; /** * 計算切分次數(shù) */ 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("進(jìn)入定時任務(wù)的方法中來清理前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、查詢所有數(shù)據(jù)
- 2、根據(jù)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("校驗(yàn)耗時"); gempUserOperationLogDao.deleteAllLists(queryDate); sw.stop(); System.out.println(sw.prettyPrint());
StopWatch '': running time (millis) = 943 | ||
---|---|---|
ms | % | Task name |
00943 | 100% | 校驗(yàn)耗時 |
總結(jié)
面對大數(shù)據(jù)量的數(shù)據(jù)批量修改、刪除操作時,不要使用jpa封裝的方法操作數(shù)據(jù),編寫原生sql效率更高且不易發(fā)生問題。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java圖像之自定義角度旋轉(zhuǎn)(實(shí)例)
這篇文章主要介紹了Java圖像之自定義角度旋轉(zhuǎn)(實(shí)例),需要的朋友可以參考下2017-09-09Java 8 Lambda 表達(dá)式比較器使用示例代碼
這篇文章主要介紹了Java 8 Lambda 表達(dá)式比較器使用示例代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-08-08