【问题标题】:Improve performance of a foreach persist call with JPA使用 JPA 提高 foreach 持久调用的性能
【发布时间】:2015-05-20 10:08:53
【问题描述】:

我们有一个类似这样的业务逻辑:

public class StuffLogic {
    @Autowired
    private Util util;
    // ...

    public void processStuffs() {
        // Simply returns a list of 50k objects
        List<Stuff> list = dao.getManyFilteredStuff();

        for (Stuff act : list) {
            act.setStatus(StatusEnum.SomeStatus);
        }

        util.mergeAll(list);
    }
}

public class Util {
    // ...

    @Transactional
    public void mergeAll(List<?> list) {
        for (Object o : entities) {
            entityManager.merge(o);
        }
    }
}

我们有很多实体(约 50000 个)并希望提高性能(根据初步测量,我们目前每秒可以处理 1000 个实体)。

你们对此有什么提示吗?

到目前为止我们所做的尝试:

  • 多线程。似乎如果我们使用多线程执行并进行处理,坚持在不同的线程中,它不会真正加快执行时间,但会减慢大约 5 倍。我们在 Oracle 上,也许它使用表锁定代替更新的行锁定,因此所有线程都必须等待。
  • 很遗憾,使用单个批量更新不是一种选择,因为我们有许多听众在引入一些魔法,而这些魔法不适用于单个更新语句。

一些技术细节:我们正在使用 Oracle、JPA/Hibernate。

任何提示将不胜感激!

【问题讨论】:

  • 我假设对persist 的调用在foreach 循环内?
  • 是的,对不起,错字。基本上我们在某处有一个实用函数,它为每个元素调用persist(),并用@Transactional 标记。
  • 请发布一些实际代码,因为目前我对交易一无所知,您使用单个循环还是多个循环涉及多少实体管理器。信息太少了……
  • 我会假设循环也在一个事务中,并且实际上只有一个事务。拥有 5000 + 1 个事务并不是很快,因为启动/提交很慢。使用批处理而不是单个提交会加快速度,您也不应该使用merge 而不是persist(后者适用于新实体)。
  • 根据要求,请使用未派生的实际代码,因为您现在无法使用(没有参数,方法名称不匹配)。

标签: java multithreading oracle hibernate jpa


【解决方案1】:

您可以做几件事,当您一次更新所有内容时,一级缓存会不断增长。这可能会增加进行脏检查所需的时间。

所以在 x 条记录之后(找到甜蜜点)对 entityManager 进行刷新和清除。

public void mergeAll(List<?> list) {
    int i = 0;
    for (Object o : entities) {
        entityManager.merge(o);
        i++:
        if (i ^ 50 == 0) {
            entityManager.flush();
            entityManger.clear();
        }
    }
}

现在您还可以指示 hibernate 批处理语句,为此您需要调整 hibernate 设置。首先有hibernate.jdbc.batch_size 来启用批处理。

 hibernate.jdbc.batch_size=50 

这应该会减少向 oracle 发起的查询数量,而不是 50 个单个查询,它将是一个包含 50 个条目的单个查询。

如果您的修改导致更新和插入,您可能需要对它们进行排序,以便 hibernate 可以将它们组合在一起并使用批处理语句。

hibernate.order_inserts=true
hibernate.order_updates=true

如果您使用版本控制,您可能还需要将 hibernate.jdbc.batch_versioned_data 设置为 true

可以在here 找到有关这些属性的好帖子。

【讨论】:

  • 酷,感谢您的意见!让我们尝试一下这些设置,看看它有多大帮助。
【解决方案2】:

你可以这篇关于批处理的文章http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/batch.html

也许您也可以考虑动态构建查询,例如:

UPDATE stuff
SET status = CASE WHEN id = 1 THEN 'status1'
            WHEN id = 2 THEN 'status2' 
            ...

【讨论】:

  • 谢谢,会看看那个文档,看起来很有希望。更新不是一个选项,因为有些侦听器不会被触发。
【解决方案3】:

假设mergeAll 在单独的事务中执行(否则,如果实体已经在持久性上下文中,则无需调用merge),那么您将有许多数据库往返(每个至少一个entity) 来获取要合并的实体。

其中一个解决方案是通过查询(与您在处理之前用于读取实体的查询相同的查询或使用构造 select e from entity where entity.id in (?, ?, ?, ...))读取所有这些实体,然后合并它们,因为实体将处于持久状态合并时的上下文。

您可能希望将此方法与持久性上下文的批量刷新/清除相结合,以实现更好的内存管理,如其他答案中所建议的那样。

【讨论】:

  • 非常感谢,我们也会检查这个想法。看起来很有希望。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-01
  • 2011-09-24
  • 1970-01-01
  • 1970-01-01
  • 2022-01-02
  • 1970-01-01
相关资源
最近更新 更多