【问题标题】:Hibernate flush slowdown in AbstractFlushingEventListener (JBoss 5.2.2 Hibernate 3.3.2)AbstractFlushingEventListener 中的休眠刷新速度减慢(JBoss 5.2.2 Hibernate 3.3.2)
【发布时间】:2015-01-31 12:16:45
【问题描述】:

我遇到了一个问题,即休眠刷新策略在处理订单上的一组订单项时会随着时间的推移而变慢。当后续更新被调用到 JBoss Seam 更新时,减速会随着时间的推移而增加。

代码的业务流程接受包含“n”行项目的订单,为业务目的修改每个行项目,在该行项目上调用 seam/hibernate update(),然后移动到下一个行项目。当调用 update() 时,Hibernate 刷新策略会立即响应。但是随着代码在行项目列表中向下移动,Hibernate 速度变慢(几乎成倍增长)。启用日志记录后,我可以看到减速开始为几秒钟,然后是一两分钟,然后是 5 分钟以上,然后是 20 分钟以上。所有处理集合中相同的订单行项目。它不会改变集合的大小,不会添加或删除任何项目。

我启用了休眠事务的跟踪,并在输出中发现 org.hibernate.event.def.AbstractFlushingEventListener 在调用之间的某处遇到问题:

prepareCollectionFlushes(session);

flushEntities(event);

我已经根据 Hibernate 创建的日志条目将其追溯到这两个方法调用之间的问题:

2014-12-02 17:50:30,808 DEBUG [org.hibernate.engine.CollectionEntry] (http-0.0.0.0-8443-2) Collection dirty: [com.companioncabinet.ccs.domain.data.Job.jobPhases#10324859]

2014-12-02 18:19:09,015 TRACE [org.hibernate.event.def.AbstractFlushingEventListener] (http-0.0.0.0-8443-2) Flushing entities and processing referenced collections

注意 17:50 的消息组,然后是 18:19 的时间间隔。代码都在 Hibernate 的 AbstractFlushingEventListner 中。我检查了 Hibernate 源代码,发现它们的代码如下所示:

prepareCollectionFlushes(session);   
// now, any collections that are initialized   
// inside this block do not get updated - they   
// are ignored until the next flush   

persistenceContext.setFlushing(true);   
try {   
   flushEntities(event);   
   flushCollections(session);   
}   
   finally {   
   persistenceContext.setFlushing(false);   
}

出现的消息来自:

prepareCollectionFlushes(session);

flushingEntities(event);

prepareCollectionFlushes 方法调用 CollectionEntry.preFlush 生成“Collection dirty”消息。

这里是所有涉及的方法:

prepareCollectionFlushes

/**  
* Initialize the flags of the CollectionEntry, including the  
* dirty check.  
*/   
private void prepareCollectionFlushes(SessionImplementor session) throws HibernateException {   
   // Initialize dirty flags for arrays + collections with composite elements   
   // and reset reached, doupdate, etc.   
   log.debug("dirty checking collections");   
   final List list = IdentityMap.entries( session.getPersistenceContext().getCollectionEntries() );   
   final int size = list.size();   
   for ( int i = 0; i < size; i++ ) {   
   Map.Entry e = ( Map.Entry ) list.get( i );   
      ( (CollectionEntry) e.getValue() ).preFlush( (PersistentCollection) e.getKey() );   
   }   
}

预刷新

public void preFlush(PersistentCollection collection) throws HibernateException {

    boolean nonMutableChange = collection.isDirty() && 
            getLoadedPersister()!=null && 
            !getLoadedPersister().isMutable();
    if (nonMutableChange) {
        throw new HibernateException(
                "changed an immutable collection instance: " + 
                MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() )
            );
    }

    dirty(collection);

    if ( log.isDebugEnabled() && collection.isDirty() && getLoadedPersister() != null ) {
        log.debug(
                "Collection dirty: " +
                MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() )
            );
    }

    setDoupdate(false);
    setDoremove(false);
    setDorecreate(false);
    setReached(false);
    setProcessed(false);
}

/**
 * Determine if the collection is "really" dirty, by checking dirtiness
 * of the collection elements, if necessary
 */
private void dirty(PersistentCollection collection) throws HibernateException {

    boolean forceDirty = collection.wasInitialized() &&
            !collection.isDirty() && //optimization
            getLoadedPersister() != null &&
            getLoadedPersister().isMutable() && //optimization
            ( collection.isDirectlyAccessible() || getLoadedPersister().getElementType().isMutable() ) && //optimization
            !collection.equalsSnapshot( getLoadedPersister() );

    if ( forceDirty ) {
        collection.dirty();
    }

}

上述块中的 collection.dirty() 将 PersistenceCollection 脏设置为 true。

flushEntities

/**  
* 1. detect any dirty entities  
* 2. schedule any entity updates  
* 3. search out any reachable collections  
*/   
private void flushEntities(FlushEvent event) throws HibernateException {   
   log.trace("Flushing entities and processing referenced collections");   
   ...remainder of code omitted since the delay happens before the above log entry appears

手头有所有代码,没有什么明显的事情发生。延迟期间代码中没有任何事情发生。一切似乎都停止了。

数据库是 PostgreSQL。我已启用所有查询的日志记录。当我将最后一个查询与延迟之前对齐时,它似乎是所有脏实体的重新加载。当我运行记录的选择时,它需要不到 1 毫秒。

我知道这些是旧版本,需要“升级”。但是,最好指出一个特定的缺陷、错误或众所周知的性能问题作为触发升级的催化剂。

感谢收看!

orm.xml

<persistence-unit-metadata>
    <persistence-unit-defaults>
        <entity-listeners>
            <entity-listener class="org.jboss.seam.security.EntitySecurityListener"/>
        </entity-listeners>
    </persistence-unit-defaults>
</persistence-unit-metadata>

persistence.xml

<persistence-unit name="DS1" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:/DS1</jta-data-source>
    <properties>
        <property name="hibernate.archive.autodetection" value="class" />
        <property name="hibernate.search.default.indexBase" value="/home/lucene/DS1" />
         <!-- Binds the EntityManagerFactory to JNDI where Seam can look it up.
        This is only relevant when the container automatically loads the persistence unit, as is the case in JBoss AS 5. -->
        <property name="jboss.entity.manager.factory.jndi.name" value="java:/DS1EntityManagerFactory"/>
    </properties>

</persistence-unit>

hibernate.properties 为空

ds.xml

<local-tx-datasource>
    <jndi-name>DS1</jndi-name>
    <connection-url>jdbc:postgresql://127.0.0.1:5432/data</connection-url>
    <driver-class>org.postgresql.Driver</driver-class>
    <user-name>a_user</user-name>
    <password>a_pw</password>
    <idle-timeout-minutes>90</idle-timeout-minutes>
    <new-connection-sql>select 1</new-connection-sql>
    <check-valid-connection-sql>select 1</check-valid-connection-sql>
</local-tx-datasource>

数据库日志摘录

2014-12-08 19:43:19 EST LOG:  duration: 0.000 ms  execute <unnamed>: update PurchaseOrder set active=$1, createdby=$2, createdStamp=$3, ...
2014-12-08 19:43:19 EST LOG:  duration: 0.000 ms  execute <unnamed>: select orderlines0_.purchaseorderid as purchas66_18_5_, ...
2014-12-08 19:49:29 EST LOG:  duration: 0.000 ms  execute <unnamed>: update PurchaseOrder set active=$1, createdby=$2, createdStamp=$3, ...
2014-12-08 19:49:29 EST LOG:  duration: 0.000 ms  execute <unnamed>: select orderlines0_.purchaseorderid as purchas66_18_5_, ...

【问题讨论】:

    标签: hibernate jpa jboss seam jboss5.x


    【解决方案1】:

    持久性上下文越大,to verify all dirty properties 花费的时间就越多。

    默认的自动脏检查机制必须遍历当前附加到当前运行会话的所有实体的所有属性。

    如果你想加快速度,你需要:

    1. switch to a custom dirty checking mechanism
    2. use bytecode enhancing(其中didn't seem to work on latest Hibernate versions)

    更新

    你的一句话引起了我的注意:

    代码的业务流程接受一个包含“n”行项目的订单, 为商业目的修改每个行项目,调用接缝/休眠 update() 在该行项目上,然后移动到下一个行项目。在 当调用到时,休眠刷新策略会立即响应 update() 已完成。

    如果您加载 N 个订单项,则无需运行更新,只要会话仍处于活动状态。脏检查机制将自动检测所有更改。

    如果您正在混合托管实体更改和问题选择,那么AUTO flush mode will trigger a flush prior to any HQL query。因此,最好安排您的代码先进行选择,然后将修改留到事务的最后一部分。

    您还可以将休眠刷新模式设置为 COMMIT:

    session.setFlushMode(FlushMode.COMMIT);
    

    【讨论】:

    • 我不确定您的帖子是否适用于此。虽然我理解这些概念,但问题是集合的大小没有改变。我已经用有关该过程的更多详细信息更新了我的问题。简而言之,在业务逻辑开始时,Hibernate 会立即响应,并且日志会显示这一点。但到了最后,开始出现减速,两个动作之间的差距从几毫秒增加到 20 多分钟。集合没有变化。第一次脏检查需要几毫秒。这里似乎有些不对劲。
    • 请发布您的 Hibernate 配置以及数据源
    • 我发布了配置。谢谢。
    • 会话实际上是在每个事务中刷新。我已经启用了所有 sql 的数据库日志记录。我已经发布了数据库日志。 db 报告它在不到 1 毫秒的时间内完成了执行。所以它不是分贝。在每次选择的结果被发送回休眠状态后,应用程序的速度会更慢。上一次我看到这种增加的减速行为是当 arraylist 用于非常大的数据集时(arraylist 复制到新列表导致随着 arraylist 的增长而减速)。
    猜你喜欢
    • 2014-02-03
    • 1970-01-01
    • 2013-10-11
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多