记一次IN语句造成的Hibernate QueryPlanCache内存泄漏排查和解决办法
如果您使用了Hibernate或Spring data jpa,且SQL中使用了IN语句,且每过一段时间JVM就抛OOM异常,那么这篇文章可能对您有用。
1.问题现象
每过三四天时间,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后的参数不同,hibernate会把其当成不同的sql进行缓存,从而缓存大量的sql导致heap内存溢出。
2.解决方案
第一种方法是添加配置,现在缓冲的sql数量最大为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 ?;
使用子查询进行优化,优化后没有再出现内存泄漏问题。
SELECT * FROM TOPIC WHERE id IN (
SELECT DISTINCT topic_id FROM TOPIC_TOPIC_TYPE WHERE type_id=1
);
但是后面使用EXPALIN发现,IN语句走的是全表扫描,性能比较低,所以又用JOIN进行了优化。
SELECT * FROM TOPIC t1 JOIN TOPIC_TOPIC_TYPE t2 ON t1.id=t2.topic_id AND t2.type_id=1;
评论