首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >H2插入性能

H2插入性能
EN

Stack Overflow用户
提问于 2020-09-22 08:08:48
回答 1查看 776关注 0票数 0

我正在开发一个带有java spring引导的web应用程序,并使用H2作为数据库。

在插入数据时,我有一些性能问题。我正确地进行了批量插入,但我注意到,经过一段时间后,插入速度慢了很多。例如,插入第一个N元素需要100秒,但是接下来的<>E 110>N元素则需要200秒来插入以下N元素,然后再使用400E 29秒等等。

我正在努力找出问题并解决它。有人能帮忙吗?

为了执行批处理,我设置了应用程序属性:

代码语言:javascript
运行
复制
spring.jpa.properties.hibernate.jdbc.batch_size=20

我要插入这个实体:

代码语言:javascript
运行
复制
@Getter
@Setter
@Entity
@Table(name = "entity_son")
public class EntitySon extends EntityFather{

    protected EntitySon (){}

   @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name ="anotherEntityId")
   private AnotherEntity AnotherEntityId;
}

它继承自该实体:

代码语言:javascript
运行
复制
@Getter
@Setter
@MappedSuperclass
public abstract class EntityFather{

    @Id
    @SequenceGenerator(name = "SEQ", initialValue = 1, allocationSize = 20, sequenceName = "EntitySequence")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ")
    @Column(name ="entityId")
    private Long entityFatherId;

}

我以这种方式生成了一个序列,使用的是液相基:

代码语言:javascript
运行
复制
databaseChangeLog:
  - changeSet:
      id: createSequence
      author: liquibase-docs
      changes:
        - createSequence:
            sequenceName: EntitySequence
            incrementBy: 20

最后,我用这种方式进行批量插入:

代码语言:javascript
运行
复制
private void saveEntitySon(List<EntitySon> entitySons){
    long BATCH_SIZE = 20L;
    long batchIter = 0;
    while(true) {
        List<EntitySon> batch = entitySons.stream().skip(batchIter*BATCH_SIZE)
                .limit(BATCH_SIZE*(batchIter+1)).collect(Collectors.toList());
        if (batch.size() < BATCH_SIZE) {
            logger.info("Saving line difference by line difference");
            for (EntitySon entitySon : batch) {
                entitySonRepository.save(entitySon)
            }
            return;
        }else{
                entitySonRepository.saveAll(batch)
        }
        batchIter++;
    }
}

我还必须指出,如果我删除并重新创建数据库,我在性能方面也会看到相同的模式。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-09-22 10:02:57

好的,您在这里混合了两件事:JDBC批处理大小JPA会话。

JDBC批处理大小将使底层JDBC数据库驱动程序批处理多个插入在一起,因此可以节省DB往返。JPA构建在JDBC之上,并在“会话”或EntityManager中管理状态。我总是喜欢将持久化实体称为“托管”实体,以明确地表明,在当前会话中,的每个实体都有某种状态的保存和管理。

警告:,我不知道流操作的内存效率有多高,我也没有查过。

您应该使用一个分析器,比如VisualVM (它是JDK的一部分),或者即使使用TaskManager,您也应该能够看到内存消耗的增长。

您在一个事务中,持久化100个实体,因此您的会话包含100个状态,然后添加越来越多的状态。这减缓了状态和垃圾收集的迭代速度。

您想要的是在JPA级别上批处理,也是。不幸的是,Spring存储库在这方面缺乏。如果您查看代码,saveAll将执行与您的循环完全相同的操作(迭代列表并调用save())。这里需要一个实际的EntityManager,这样就可以将语句刷新到数据库中,然后清除会话以删除所有状态:

代码语言:javascript
运行
复制
private void saveEntitySon(List<EntitySon> entitySons){
    long BATCH_SIZE = 20L;

    for (long i = 0L; i < entitySons.size(); i++) {

        if (i > 0 && i % BATCH_SIZE == 0) {
            entitySonRepository.flush(); // Could also use EntityManager, doesn't matter
            entityManager.clear(); // This will also detach all entities! So make sure you need to reload them if you want to use them!
        }

        EntitySon entitySon = entitySons.get(i);
        // Using repo so Spring Data JPA events get triggered
        entitySonRepository.save(entitySon);
    }
    
    // Flush out remainder
    entitySonRepository.flush();
    entityManager.clear();
} 

正如评论所述,请注意clear()将分离所有实体。如果在批处理之前加载/持久化实体并希望在批处理之后使用该实体,则会出现问题。但通常情况下,这样的批处理作业无论如何都会自行运行。

编辑:我假设您希望在一个事务中完成所有操作,因此您将“全部或什么都不做”持久化。当然,数据库端也存在状态管理开销,长时间运行的事务会导致冲突。

在处理JPA时,总是的一个好主意,查看Vlad的博客,他有一篇关于批处理这里的文章。

Hibernate用户指南还有一个关于批处理的章节。

请注意,Spring对于许多日常事务来说是一个很好的、舒适的抽象,但它不允许进行精确的控制,也不足以满足许多性能关键或更复杂的任务。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64005534

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档