【问题标题】:Spring Boot JPARepository performance on save()save() 上的 Spring Boot JPARepository 性能
【发布时间】:2018-08-21 11:06:22
【问题描述】:

我的 Spring Boot 应用程序在插入数据时性能非常慢。

我正在从一个数据库中提取大量数据子集并将数据插入到另一个数据库中。

以下是我的实体。

@Entity
@Table(name = "element")
public class VXMLElementHistorical {

@Id
@Column(name = "elementid")   
private long elementid;

@Column(name = "elementname")
private String elementname; 

Getter/Setter methods...    

我已经配置了一个 JPA 存储库

public interface ElementRepository extends JpaRepository<Element, Long> {

}

并用我的对象调用 save() 方法

@Transactional 
public void processData(List<sElement> hostElements) 
throws DataAccessException { 

List<Element> elements = new ArrayList<Element>();    

for (int i = 0; i < hostElements.size(); i++) {
        Element element = new Element();
        element.setElementid(hostElements.get(i).getElementid());
        element.setElementname(hostElements.get(i).getElementname());
        elements.add(element);
    }

   try{
   elementRepository.save(elements);{
   //catch etc...

}

发生的情况是,对于每个项目,执行插入需要 6 到 12 秒。我打开了休眠跟踪记录和统计,当我调用保存函数时发生的事情是休眠执行两个查询,一个选择和一个插入。选择查询占用了总时间的 99%。

我已直接在数据库上运行选择查询,结果以纳秒为单位返回。这让我相信这不是索引问题,但我不是 DBA。

我在我的开发环境中创建了一个负载测试,并且在负载大小相似的情况下,总的处理时间远不及我的生产环境中的时间。

有什么建议吗?

【问题讨论】:

  • 我们在项目中使用spring data jpa,插入元素从未如此耗时。可能,根本原因与spring data jpa不同
  • 首先你不应该转换,把它放在一个列表中,然后保存所有元素。您已经有效地复制了所有元素(这将增加内存)。此外,您在一笔大交易中做所有事情,这也会导致问题。相反,直接保存创建的元素和每个 x 元素(比如 50)你做一个flushclear。最好您还将刷新模式设置为手动(以防止脏检查和刷新)。

标签: java spring hibernate spring-boot spring-data-jpa


【解决方案1】:

不要创建元素列表并保存它们,而是保存单个元素。时不时地执行flushclear 以防止脏检查成为瓶颈。

@PersistenceContext
private EntityManager entityManager;

@Transactional 
public void processData(List<sElement> hostElements) 
throws DataAccessException {     

for (int i = 0; i < hostElements.size(); i++) {
        Element element = new Element();
        element.setElementid(hostElements.get(i).getElementid());
        element.setElementname(hostElements.get(i).getElementname());
        elementRepository.save(element)
        if ( (i % 50) == 0) {
            entityManager.flush();
            entityManager.clear();
        }
}
entityManager.flush(); // flush the last records.

您想刷新 + 清除每个 x 元素(这里是 50,但您可能想找到自己的最佳数字。

现在,当您使用 Spring Boot 时,您可能还想添加一些额外的属性。就像配置批量大小一样。

spring.jpa.properties.hibernate.jdbc.batch_size=50 

如果您的 JDBC 驱动程序支持,这将把 50 个单插入语句转换为 1 个大批量插入。 IE。 50 个插入到 1 个插入。

另见https://vladmihalcea.com/how-to-batch-insert-and-update-statements-with-hibernate/

【讨论】:

    【解决方案2】:

    作为@M。 Deinum 在评论中说,您可以通过调用flush()clear() 来改进,如下所示。

    int i = 0;
    for(Element element: elements) {
        dao.save(element);
        if(++i % 20 == 0) {
            dao.flushAndClear();
        }
    
    }
    

    【讨论】:

    • 由于大部分时间用于加载数据,因此清除缓存实际上可能会降低性能。
    【解决方案3】:

    由于加载实体似乎是瓶颈,您真的只想进行插入,即您知道数据库中不存在实体,您可能不应该使用 Spring Data JPA 的标准 save 方法。

    原因是它执行merge 触发Hibernate 加载可能已经存在于数据库中的实体。

    相反,将custom method 添加到您的存储库中,它在实体管理器上执行persist。由于您提前设置了Id,因此请确保您具有版本属性,以便 Hibernate 可以确定这确实是一个新实体。

    这应该会使选择消失。

    其他答案中给出的其他建议值得考虑作为第二步:

    • 启用批处理。
    • 实验中间刷新和清除会话。
    • 一次保存一个实例而不将它们收集到一个集合中,因为对mergepersist 的调用实际上不会触发写入数据库,而只有刷新会触发(这是一种简化,但它应为这种情况做)

    【讨论】:

      猜你喜欢
      • 2019-10-30
      • 1970-01-01
      • 2020-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-10
      • 2019-06-16
      • 2020-03-09
      相关资源
      最近更新 更多