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

從Problem Suspect 1 中可以看出一個SessionFactoryImpl類型的實例占用了93.14%的內存。點擊Details,可以看到SessionFactoryImpl類中有一個queryPlanCache對象,占用了大量的內存。

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