gpt4 book ai didi

hibernate - Hibernate使用flushMode = AUTO查询要慢得多,直到调用clear()为止

转载 作者:行者123 更新时间:2023-12-03 12:56:33 28 4
gpt4 key购买 nike

我有一个使用Hibernate(通过JPA)的长期运行(但相当简单)的应用程序。运行时,它正在经历相当大的减速。我已经缩小到偶尔需要entityManager.clear()调用的范围。当Hibernate的实体管理器跟踪100,000个实体时,它的速度要比仅跟踪几个实体时慢100倍(请参见下面的结果)。 我的问题是:为什么Hiberate在跟踪大量实体时会放慢速度呢?还有其他解决方法吗?

!!!更新:我已经能够将其范围缩小到Hibernate的自动刷新代码。 !!!

专门针对org.hibernate.event.internal.AbstractFlushingEventListenerflushEntities()方法(至少在Hibernate 4.1.1.Final中)。在其中,存在一个循环,在持久性上下文中循环访问所有实体,围绕刷新它们中的每一个执行一些广泛的检查(即使在我的示例中所有实体都已刷新!)。

因此,部分回答了我的问题的第二部分,可以通过将刷新模式设置为查询上的FlushModeType.COMMIT来解决性能问题(请参见下面的更新结果)。例如

Place place = em.createQuery("from Place where name = :name", Place.class)
.setParameter("name", name)
.setFlushMode(FlushModeType.COMMIT) // <-- yay!
.getSingleResult();

...但是,这似乎是一个非常丑陋的解决方案-将知道事情是否已刷新到查询方法的责任交给了责任,而不是将其保留在更新方法中。这也几乎意味着我要么必须在所有查询方法上将刷新模式设置为COMMIT,要么更可能是在EntityManager上对其进行设置。

这让我感到奇怪:这是预期的行为吗?我在刷新或在定义实体时做错了什么吗?还是这是Hibernate的限制(或可能是Bug)?

我用来解决问题的示例代码如下:

测试实体

@Entity @Table(name="place") @Immutable
public class Place {
private Long _id;
private String _name;

@Id @GeneratedValue
public Long getId() { return _id; }
public void setId(Long id) { _id = id; }

@Basic(optional=false) @Column(name="name", length=700,
updatable=false, nullable=false, unique=true,
columnDefinition="varchar(700) character set 'ascii' not null")
public String getName() { return _name; }
public void setName(String name) { _name = name; }

@Override
public boolean equals(Object o) { /* ... */ }

@Override
public int hashCode() { return getName().hashCode(); }
}

基准代码

我拥有的测试代码会生成100000个随机地名并将其插入。然后按名称随机查询出5000个。名称列上有一个索引。

Place place = em.createQuery(
"select p from Place p where p.name = :name", Place.class)
.setParameter("name", name)
.getSingleResult();

为了进行比较,并确保它不是数据库中的内容,我在单独的随机选择的5000个地名上运行了以下基于JDBC的查询(在 em.unwrap(Session.class).doWork(...)下):

PreparedStatement ps = c.prepareStatement(
"select id, name from place where name = ?");
ps.setString(1, name);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Place place = new Place();
place.setId(rs.getLong(1));
place.setName(rs.getString(2));
}
rs.close();
ps.close();

(请注意,我确实为基准的5000个查询中的每个查询创建并关闭了PreparedStatement)。

结果

以下所有结果平均超过5000个查询。 JVM被赋予了 -Xmx1G
Seconds/Query    Approach
0.000160s JDBC
0.000286s Hibernate calling clear() after import and every 100 queries
0.000653s Hibernate calling clear() once after the import
0.012533s Hibernate w/o calling clear() at all
0.000292s Hibernate w/o calling clear(), and with flush-mode COMMIT

其他观察结果:在Hibernate查询期间(不进行任何明确的调用),java进程将内核的利用率接近100%。 JVM从未超过500MB堆。查询期间也有很多GC Activity ,但是CPU利用率显然由Hibernate代码支配。

最佳答案

But mainly I'm curious why Hibernate seems to exhibit O(n) or even O(n^2) lookups for the queries--seems like it should be able to use a hashtable or binary-tree under the hood to keep the queries fast. Notice the 2-orders-of-magnitude difference when its tracking 100000 entities vs 100 entities.



O(n²)复杂度来自查询处理方式。由于Hibernate在内部尽可能长地延迟更新和插入(以利用机会将相似的更新/插入分组在一起,特别是在设置对象的多个属性的情况下)。

因此,在保存查询数据库中的对象之前,Hibernate必须检测所有对象更改并刷新所有更改。这里的问题是,休眠状态也正在进行一些通知和拦截。因此,它将遍历持久性上下文管理的每个实体对象。即使对象本身不是可变的,它也可能包含可变的对象甚至引用集合。

此外,拦截机制还允许您访问任何被认为是脏对象的对象,以允许您自己的代码实现其他脏度检查或执行其他计算,例如计算总和,平均值,记录其他信息等。

但是让我们看一下代码:

准备查询的冲洗调用结果如下:
DefaultFlushEventListener.onFlush(..)

-> AbstractFlushingEventListener.flushEverythingToExecution(事件)
-> AbstractFlushingEventListener.prepareEntityFlushes(..)

该实现使用:
for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getEntityEntries() ) ) {
EntityEntry entry = (EntityEntry) me.getValue();
Status status = entry.getStatus();
if ( status == Status.MANAGED || status == Status.SAVING || status == Status.READ_ONLY ) {
cascadeOnFlush( session, entry.getPersister(), me.getKey(), anything );
}
}

如您所见,持久性上下文中所有实体的映射都被检索和迭代。

这意味着对于查询的每次调用,您都将遍历所有以前的结果以检查脏对象。甚至更多的cascadeOnFlush都会创建一个新的Object,并做更多的事情。这是cascadeOnFlush的代码:
private void cascadeOnFlush(EventSource session, EntityPersister persister, Object object, Object anything)
throws HibernateException {
session.getPersistenceContext().incrementCascadeLevel();
try {
new Cascade( getCascadingAction(), Cascade.BEFORE_FLUSH, session )
.cascade( persister, object, anything );
}
finally {
session.getPersistenceContext().decrementCascadeLevel();
}
}

这就是解释。每次发出查询时,Hibernate只会检查由持久性上下文管理的每个对象。

因此,对于每个在这里阅读内容的人,都是复杂度计算:
1.查询:0个实体
2.查询:1个实体
3.查询:2个实体
..
100.查询:100个实体

..
100k +1查询:100k条目

因此我们有O(0 + 1 + 2 ... + n)= O(n(n + 1)/ 2)= O(n²)。

这解释了您的观察。为了保持较小的cpu和内存占用量,休眠管理的持久化上下文应保持尽可能小。让Hibernate管理超过100个或1000个实体会大大降低Hibernate的速度。在这里,应该考虑更改刷新模式,使用第二个 session 进行查询,然后使用一个 session 进行更改(如果可能的话),或者使用StatelessSession。

因此,您的观察是正确的,这是O(n²)的结果。

关于hibernate - Hibernate使用flushMode = AUTO查询要慢得多,直到调用clear()为止,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10143880/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com