記一次IN語(yǔ)句造成的Hibernate QueryPlanCache內(nèi)存泄漏排查和解決辦法
如果您使用了Hibernate或Spring data jpa,且SQL中使用了IN語(yǔ)句,且每過(guò)一段時(shí)間JVM就拋OOM異常,那么這篇文章可能對(duì)您有用。
1.問(wèn)題現(xiàn)象
每過(guò)三四天時(shí)間,JVM就拋出OOM異常,后臺(tái)進(jìn)程掛掉,前端無(wú)法訪問(wèn)。
2.問(wèn)題排查
首先,查找一下后臺(tái)進(jìn)程的進(jìn)程號(hào)
ps -ef | grep "程序名"
然后,使用jmap命令生成內(nèi)存鏡像文件
jmap -dump:live,format=b,file=heap.hprof 4447
最后,使用 MAT 對(duì)內(nèi)存鏡像文件進(jìn)行分析,分析結(jié)果如下圖所示:

從Problem Suspect 1 中可以看出一個(gè)SessionFactoryImpl類型的實(shí)例占用了93.14%的內(nèi)存。點(diǎn)擊Details,可以看到SessionFactoryImpl類中有一個(gè)queryPlanCache對(duì)象,占用了大量的內(nèi)存。

百度了一下queryPlanCache內(nèi)存泄漏,找了一篇和我問(wèn)題相似的文章 《Hibernate sessionFactoryImpl QueryPlanCache 內(nèi)存過(guò)大導(dǎo)致內(nèi)存泄漏》 。文章提到了QueryPlanCache會(huì)緩存sql,以便于后邊的相同的sql重復(fù)編譯,如果in后的參數(shù)不同,hibernate會(huì)把其當(dāng)成不同的sql進(jìn)行緩存,從而緩存大量的sql導(dǎo)致heap內(nèi)存溢出。
2.解決方案
第一種方法是添加配置,現(xiàn)在緩沖的sql數(shù)量最大為64(但是我用的spring 1.57.release版本添加該配置沒(méi)有用)
spring:
jpa:
? properties:
? ? hibernate:
? ? ? query:
? ? ? ? plan_cache_max_size: 64
? ? ? ? plan_parameter_metadata_max_size: 32
所以,我對(duì)SQL進(jìn)行了改寫(xiě),之前我的SQL邏輯是這樣的
# 1.從TOPIC_TOPIC_TYPE表中將帖子類型為1的帖子ID找出來(lái)
SELECT DISTINCT t.topic_id FROM TOPIC_TOPIC_TYPE t WHERE t.type_id=1
# 2.從TOPIC表中按帖子ID進(jìn)行查詢
SELECT * FROM TOPIC t WHERE t.id IN ?;
使用子查詢進(jìn)行優(yōu)化,優(yōu)化后沒(méi)有再出現(xiàn)內(nèi)存泄漏問(wèn)題。
SELECT * FROM TOPIC WHERE id IN (
SELECT DISTINCT topic_id FROM TOPIC_TOPIC_TYPE WHERE type_id=1
);
但是后面使用EXPALIN發(fā)現(xiàn),IN語(yǔ)句走的是全表掃描,性能比較低,所以又用JOIN進(jìn)行了優(yōu)化。
SELECT * FROM TOPIC t1 JOIN TOPIC_TOPIC_TYPE t2 ON t1.id=t2.topic_id AND t2.type_id=1;
