H2插入性能
H2 Insert performances
我正在开发一个带有 java spring 引导的 Web 应用程序,我正在使用 H2 作为数据库。
我在插入数据时遇到了一些性能问题。我正确地进行了批量插入,但我注意到一段时间后插入速度变慢了很多。例如,插入第一个 N 个元素需要 100 秒,但随后需要 200 秒插入以下 N 个元素,然后 400 秒插入下一个 N 个元素,依此类推。
我正在努力寻找并解决问题。有人可以帮忙吗?
为了进行批处理,我设置了应用程序属性:
spring.jpa.properties.hibernate.jdbc.batch_size=20
我正在插入这个实体:
@Getter
@Setter
@Entity
@Table(name = "entity_son")
public class EntitySon extends EntityFather{
protected EntitySon (){}
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name ="anotherEntityId")
private AnotherEntity AnotherEntityId;
}
继承自该实体:
@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;
}
我正在使用 liquibase 以这种方式生成一个序列:
databaseChangeLog:
- changeSet:
id: createSequence
author: liquibase-docs
changes:
- createSequence:
sequenceName: EntitySequence
incrementBy: 20
最后我以这种方式进行批量插入:
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++;
}
}
我还必须提到,如果我删除并重新创建数据库,我会在性能上看到相同的模式。
好的,所以你在这里混合了两件事:JDBC 批量大小 和 JPA 会话。
JDBC 批量大小将使底层 JDBC 数据库驱动程序将多个插入一起批量插入,因此您可以节省 DB round-trips。
JPA 建立在 JDBC 之上,并在“会话”或 EntityManager 中管理状态。我总是喜欢将持久实体称为“托管”实体,以明确表示在当前会话中有一些东西可以保存和管理 每个实体 的状态。
警告:我不确定流操作的内存效率如何,我没有查找。
您应该使用分析器,例如 VisualVM(它是 JDK 的一部分),或者甚至可以使用 Windows TaskManager,您应该能够看到您的内存消耗增长。
你在一个事务中,你坚持 100 个实体,所以你的会话包含 100 个状态,然后你添加越来越多。这会减慢状态和垃圾收集的迭代速度。
您想要的是 JPA 级别的批处理。不幸的是,Spring JPA 存储库在这方面缺乏。如果您查看代码,saveAll 与您的循环执行完全相同的操作(遍历列表并调用 save()
)。
您在这里需要一个实际的 EntityManager,以便您可以将语句刷新到数据库,然后清除会话以删除所有状态:
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()
将分离所有实体。如果您在批处理之前 load/persist 一个实体并想在之后使用它,这就是一个问题。但通常这样的批处理作业本身会 运行。
编辑:
我假设你想在一次交易中做所有事情,所以你会坚持“全有或全无”。但当然,数据库端也有状态管理开销,长 运行ning 事务会导致冲突。
在处理 JPA 时,总是查看 Vlad Mihalcea 的博客是个好主意,他有一篇关于批处理的文章 here。
Hibernate User Guide也有一章是关于批处理的。
请注意,Spring Data JPA 对于许多 day-to-day 事物来说是一个很好的、舒适的抽象,但它不允许精确控制,并且对于许多性能关键或更多的东西来说是不够的复杂的任务。
我正在开发一个带有 java spring 引导的 Web 应用程序,我正在使用 H2 作为数据库。
我在插入数据时遇到了一些性能问题。我正确地进行了批量插入,但我注意到一段时间后插入速度变慢了很多。例如,插入第一个 N 个元素需要 100 秒,但随后需要 200 秒插入以下 N 个元素,然后 400 秒插入下一个 N 个元素,依此类推。
我正在努力寻找并解决问题。有人可以帮忙吗?
为了进行批处理,我设置了应用程序属性:
spring.jpa.properties.hibernate.jdbc.batch_size=20
我正在插入这个实体:
@Getter
@Setter
@Entity
@Table(name = "entity_son")
public class EntitySon extends EntityFather{
protected EntitySon (){}
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name ="anotherEntityId")
private AnotherEntity AnotherEntityId;
}
继承自该实体:
@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;
}
我正在使用 liquibase 以这种方式生成一个序列:
databaseChangeLog:
- changeSet:
id: createSequence
author: liquibase-docs
changes:
- createSequence:
sequenceName: EntitySequence
incrementBy: 20
最后我以这种方式进行批量插入:
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++;
}
}
我还必须提到,如果我删除并重新创建数据库,我会在性能上看到相同的模式。
好的,所以你在这里混合了两件事:JDBC 批量大小 和 JPA 会话。
JDBC 批量大小将使底层 JDBC 数据库驱动程序将多个插入一起批量插入,因此您可以节省 DB round-trips。 JPA 建立在 JDBC 之上,并在“会话”或 EntityManager 中管理状态。我总是喜欢将持久实体称为“托管”实体,以明确表示在当前会话中有一些东西可以保存和管理 每个实体 的状态。
警告:我不确定流操作的内存效率如何,我没有查找。
您应该使用分析器,例如 VisualVM(它是 JDK 的一部分),或者甚至可以使用 Windows TaskManager,您应该能够看到您的内存消耗增长。
你在一个事务中,你坚持 100 个实体,所以你的会话包含 100 个状态,然后你添加越来越多。这会减慢状态和垃圾收集的迭代速度。
您想要的是 JPA 级别的批处理。不幸的是,Spring JPA 存储库在这方面缺乏。如果您查看代码,saveAll 与您的循环执行完全相同的操作(遍历列表并调用 save()
)。
您在这里需要一个实际的 EntityManager,以便您可以将语句刷新到数据库,然后清除会话以删除所有状态:
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()
将分离所有实体。如果您在批处理之前 load/persist 一个实体并想在之后使用它,这就是一个问题。但通常这样的批处理作业本身会 运行。
编辑: 我假设你想在一次交易中做所有事情,所以你会坚持“全有或全无”。但当然,数据库端也有状态管理开销,长 运行ning 事务会导致冲突。
在处理 JPA 时,总是查看 Vlad Mihalcea 的博客是个好主意,他有一篇关于批处理的文章 here。
Hibernate User Guide也有一章是关于批处理的。
请注意,Spring Data JPA 对于许多 day-to-day 事物来说是一个很好的、舒适的抽象,但它不允许精确控制,并且对于许多性能关键或更多的东西来说是不够的复杂的任务。