我正在用最新版本的Spring Boot编写一个应用程序。我最近遇到了堆增长的问题,这不能被垃圾收集。Eclipse MAT对堆的分析显示,在运行应用程序的一个小时内,堆增长到630MB,而Hibernate的SessionFactoryImpl使用了整个堆的75%以上。
IS在查询计划缓存周围寻找可能的源,但我找到的唯一源是this,但这并没有发挥作用。
spring.jpa.properties.hibernate.query.plan_cache_max_soft_references=1024
spring.jpa.properties.hibernate.query.plan_cache_max_strong_references=64
数据库查询都是由Spring的Query magic生成的,使用的存储库接口如本文档所示。使用这种技术生成的查询大约有20种。没有使用其他原生SQL或HQL。示例:
@Transactional
public interface TrendingTopicRepository extends JpaRepository<TrendingTopic, Integer> {
List<TrendingTopic> findByNameAndSource(String name, String source);
List<TrendingTopic> findByDateBetween(Date dateStart, Date dateEnd);
Long countByDateBetweenAndName(Date dateStart, Date dateEnd, String name);
}
或
List<SomeObject> findByNameAndUrlIn(String name, Collection<String> urls);
作为IN使用示例。
问题是:为什么查询计划缓存会不断增长(它不会停止,而是以满堆结束)?如何防止这种情况?有人遇到过类似的问题吗?
版本:
- Spring Boot1.2.5
- 休眠4.3.10
9条答案
按热度按时间nfg76nw01#
我也遇到过这个问题,基本上可以归结为IN子句中的值的数量是可变的,而Hibernate试图缓存这些查询计划。
关于这个主题,有两篇很棒的博客文章。The first:
在项目中使用Hibernate 4.2和MySQL,并包含子句内查询,例如:
select t from Thing t where t.id in (?)
Hibernate会缓存这些解析的HQL查询。具体来说,Hibernate
SessionFactoryImpl
包含QueryPlanCache
、queryPlanCache
和parameterMetadataCache
。但事实证明,当in-clause的参数数量很大且变化不定时,这将是一个问题。这些缓存会随着每个不同的查询而增长,因此这个包含6000个参数的查询与6001不同。
子句内查询会扩充为集合中的参数数目。查询计划中会包含查询中每个参数的中继数据,包括产生的名称,例如x10_、x11_等。
假设子句内参数计数有4000个不同的变化,每个变化平均有4000个参数,每个参数的查询元数据在内存中迅速增加,填满了堆,因为它不能被垃圾收集。
这种情况一直持续到查询参数计数中的所有不同变体都被缓存或者JVM耗尽堆内存并开始抛出java.lang.OutOfMemoryError:Java堆空间。
可以选择避免使用in-clause,也可以对参数使用固定的集合大小(或至少使用较小的大小)。
若要设定查询计划快取大小上限,请参阅属性
hibernate.query.plan_cache_max_size
,预设值为2048
(对于具有许多参数的查询而言,很容易太大)。和second(也从第一个引用):
Hibernate内部使用MapHQL语句的cache(作为字符串)设置为query plans。缓存由一个有界Map组成,默认情况下限制为2048个元素(可配置)。所有HQL查询都通过此缓存加载。如果未命中,该条目会自动添加到缓存中。这使得它非常容易发生系统颠簸-在这种情况下,我们不断地将新条目放入缓存,而从不重用它们,从而阻止缓存带来任何性能提升(它甚至增加了一些缓存管理开销)。更糟糕的是,很难偶然地检测到这种情况-您必须显式地分析缓存以便注意到那里存在问题。稍后我将简单介绍如何实现这一点。
因此高速缓存抖动是由大量新查询产生的结果。这可能是由许多问题引起的。我见过的两个最常见的问题是-Hibernate中的错误,它导致参数在JPQL语句中呈现,而不是作为参数传递,以及使用“in”子句。
由于Hibernate中存在一些不明显的错误,有时会出现参数未被正确处理并被呈现到JPQL查询中的情况(例如, checkout HHH-6280)。如果您有一个受此类缺陷影响的查询,并且该查询的执行率很高,则它将使您的查询计划缓存出现问题,因为生成的每个JPQL查询几乎都是唯一的(例如,包含您的实体的ID)。
第二个问题在于Hibernate处理带有“in”子句的查询的方式(例如,给我所有公司ID字段为1,2,10,18之一的人员实体)。对于“in”子句中的每个不同数量的参数,Hibernate将生成一个不同的查询-例如,
select x from Person x where x.company.id in (:id0_)
表示1个参数,select x from Person x where x.company.id in (:id0_, :id1_)
,用于2个参数,依此类推。就查询计划缓存而言,所有这些查询都被视为不同的查询,从而再次导致缓存抖动。您可能可以通过编写一个实用程序类来解决此问题,以便仅生成特定数量的参数-例如1、10、100 200、500、1000。例如,如果您传递22个参数,它将返回一个包含100个元素的列表,其中包含22个参数,其余78个参数设置为不可能的值(例如,-1表示用于外键的ID)。我同意这是一个丑陋的黑客,但可以完成工作。因此,您的缓存中最多只有6个唯一查询,从而减少了系统颠簸。那么,如何发现问题呢?您可以编写一些额外的代码,并通过缓存中的条目数(例如,通过JMX)公开度量,调整日志记录并分析日志等。如果您不想(或不能)修改应用程序,则可以转储堆并对其运行此OQL查询(例如,使用mat):
SELECT l.query.toString() FROM INSTANCEOF org.hibernate.engine.query.spi.QueryPlanCache$HQLQueryPlanKey l
。它将输出当前位于堆上任何查询计划缓存中的所有查询。应该很容易发现您是否受到上述任何问题的影响。至于性能的影响,很难说,因为它取决于太多的因素。我见过一个非常琐碎的查询,在创建一个新的HQL查询计划时会花费10-20毫秒的开销。一般来说,如果某个地方有缓存,那么一定有一个很好的理由a-一次未命中可能代价很高,所以你应该尽可能地避免未命中。最后但并非最不重要的是,您的数据库还必须处理大量的唯一SQL语句-导致它解析这些语句并且可能为每个语句创建不同的执行计划。
1aaf6o9v2#
我在IN查询中有很多(〉10000)参数也有同样的问题。我的参数的数量总是不同的,我不能预测这一点,我的
QueryCachePlan
增长太快了。对于支持执行计划高速缓存的数据库系统,如果可能的IN子句参数的数量减少,则命中高速缓存的机会更大。
幸运的是,5.2.18及更高版本的Hibernate提供了一种解决方案,可以在IN子句中填充参数。
Hibernate可以将绑定参数扩展为2的幂:4、8、16、32、64。这样,具有5、6或7个绑定参数的IN子句将使用8个IN子句,因此可以重用其执行计划。
如果要激活此功能,需要将此属性设置为true
hibernate.query.in_clause_parameter_padding=true
。如需详细信息,请参阅this article、atlassian。
z8dt9xmd3#
我在使用Spring Boot 1.5.7和Spring Data(Hibernate)时遇到了完全相同的问题,下面的配置解决了这个问题(内存泄漏):
5lhxktic4#
从Hibernate 5.2.12开始,可以使用以下命令指定Hibernate配置属性,以更改将文字绑定到底层JDBC预准备语句得方式:
在Java文档中,此配置属性有3个设置
1.自动(默认)
q3qa4bjr5#
我也遇到过类似的问题,这是因为您在创建查询时没有使用PreparedStatement。因此,对于每个具有不同参数的查询,它都会创建一个执行计划并将其缓存。如果您使用了PreparedStatement,那么您应该会看到内存使用量的大幅提高。
7vux5j2d6#
TL;DR:尝试将IN()查询替换为ANY()或将其删除
说明:
如果一个查询包含IN(...),那么会为IN(...)中的每一个值创建一个计划,因为 query 每次都是不同的。所以如果你有IN('a','b','c')和IN('a','b','c','d','e')-这是两个不同的查询字符串/计划要缓存。这个answer告诉了更多关于它的信息。
在ANY(...)的情况下,可以传递单个(数组)参数,因此查询字符串将保持不变,并且预准备语句计划将被缓存一次(下面给出的示例)。
原因:
此行可能导致以下问题:
因为它会为“urls”集合中的每个值生成不同的IN()查询。
警告:
您可能在不编写IN()查询的情况下,甚至在不知道它的情况下,就使用了它。
ORM(如Hibernate)可能会在后台生成它们-有时在意外的地方,有时以非最佳的方式。因此,考虑启用查询日志以查看您的实际查询。
修正:
下面是一个(伪)代码,可以解决这个问题:
"但是"
不要把任何解决方案当作现成的答案。确保在投入生产之前在实际/大数据上测试最终性能--无论你选择哪种答案。为什么?因为IN和ANY都有优缺点,如果使用不当,它们会带来严重的性能问题(请参见下面参考资料中的示例)。还确保使用parameter binding以避免安全问题。
参考资料:
100x faster Postgres performance by changing 1 line-任意(ARRAY[])与任意(VALUES())的性能比较
Index not used with =any() but used with in-IN和ANY的不同性能
Understanding SQL Server query plan cache
希望这能有所帮助。无论是否有效,请务必留下反馈--以便帮助像你这样的人。谢谢!
rkue9o1l7#
我在这个queryPlanCache上遇到了一个大问题,所以我做了一个Hibernate缓存监视器来查看queryPlanCache中的查询。我在QA环境中每5分钟使用一次Spring任务。我发现我必须更改哪些IN查询来解决缓存问题。详细信息如下:我正在使用Hibernate 4.2.18,我不知道是否会对其他版本有用。
izkcnapc8#
我们还有一个堆使用率不断增长的QueryPlanCache。我们重写了IN查询,此外,我们还有使用自定义类型的查询。结果是Hibernate类CustomType没有正确实现equals和hashCode,从而为每个查询示例创建了一个新键。这个问题现在在Hibernate 5.3中得到了解决。请参见https://hibernate.atlassian.net/browse/HHH-12463。您仍然需要正确实现equals/hashCode以使其正常工作。
3gtaxfhh9#
我们曾经遇到过这个问题,查询计划缓存增长过快,旧的gen堆也随之增长,因为gc无法收集它。罪魁祸首是JPA查询在IN子句中使用了超过200000个id。为了优化查询,我们使用了连接,而不是从一个表中提取id并在其他表选择查询中传递这些id。