Spring?JPA?deleteInBatch導(dǎo)致StackOverflow問題
數(shù)據(jù)庫日志庫定時(shí)清理日志數(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)入定時(shí)任務(wù)的方法中來清理前30天的操作日志================");
List<GempUserOperationLogPO> polist = gempUserOperationLogDao.findByCreateTimeBefore(queryDate);
gempUserOperationLogDao.deleteInBatch(polist);
}catch (Exception e){
log.info("清理前30天的操作日志出錯(cuò)================"+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ù)量過大時(shí)就會(huì)導(dǎo)致內(nèi)存溢出。
三、其他嘗試
1、循環(huán)遍歷刪除delete
List<GempUserOperationLogPO> polist = gempUserOperationLogDao.findByCreateTimeBefore(queryDate);
polist.forEach(x->{
gempUserOperationLogDao.delete(x);
});
四十萬數(shù)據(jù)等不起…
2、分批次刪除
//按每1000個(gè)一組分割
private static final Integer MAX_NUMBER = 1000;
/**
* 計(jì)算切分次數(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)入定時(shí)任務(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天的操作日志出錯(cuò)================"+e.getMessage());
}
}
還是等不起…
3、直接全部刪除
gempUserOperationLogDao.deleteAll();
deleteAll()底層執(zhí)行邏輯:
- 1、查詢所有數(shù)據(jù)
- 2、根據(jù)id逐一刪除
與方式一沒有區(qū)別,等到天荒地老
四、解決方案
原生sql刪除,秒級(jí)方案
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)耗時(shí)");
gempUserOperationLogDao.deleteAllLists(queryDate);
sw.stop();
System.out.println(sw.prettyPrint());
| StopWatch '': running time (millis) = 943 | ||
|---|---|---|
| ms | % | Task name |
| 00943 | 100% | 校驗(yàn)耗時(shí) |
總結(jié)
面對大數(shù)據(jù)量的數(shù)據(jù)批量修改、刪除操作時(shí),不要使用jpa封裝的方法操作數(shù)據(jù),編寫原生sql效率更高且不易發(fā)生問題。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)字符串和日期類型相互轉(zhuǎn)換的方法
這篇文章主要介紹了java實(shí)現(xiàn)字符串和日期類型相互轉(zhuǎn)換的方法,涉及java針對日期與字符串的轉(zhuǎn)換與運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下2017-02-02
Java輸出通過InetAddress獲得的IP地址數(shù)組詳細(xì)解析
由于byte被認(rèn)為是unsigned byte,所以最高位的1將會(huì)被解釋為符號(hào)位,另外Java中存儲(chǔ)是按照補(bǔ)碼存儲(chǔ),所以1000 0111會(huì)被認(rèn)為是補(bǔ)碼形式,轉(zhuǎn)換成原碼便是1111 0001,轉(zhuǎn)換成十進(jìn)制數(shù)便是-1212013-09-09
淺談Java中的n種隨機(jī)數(shù)產(chǎn)生辦法
眾所周知,隨機(jī)數(shù)是任何一種編程語言最基本的特征之一。而生成隨機(jī)數(shù)的基本方式也是相同的:產(chǎn)生一個(gè)0到1之間的隨機(jī)數(shù)。看似簡單,但有時(shí)我們也會(huì)忽略了一些有趣的功能。2015-09-09
java連接mysql數(shù)據(jù)庫學(xué)習(xí)示例
這篇文章主要介紹了java連接mysql數(shù)據(jù)庫學(xué)習(xí)示例,需要的朋友可以參考下2014-03-03
解決SpringMvc中普通類注入Service為null的問題
這篇文章主要介紹了解決SpringMvc中普通類注入Service為null的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
使用eclipse創(chuàng)建java項(xiàng)目的方法
這篇文章主要為大家詳細(xì)介紹了使用eclipse創(chuàng)建java項(xiàng)目的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04

