java刷新缓存flush_关于java:在每100行10000中使用flush()方法会减慢事务速度

我有一个示例项目,其中将spring-boot与spring-data-jpa和postgres db与一个表一起使用。

我试图将循环中的INSERT 1万条记录插入表中并测量执行时间-从每100条记录的EntityManager类启用或禁用flush()方法。

预期的结果是,启用了flush()方法的执行时间比禁用了flush()方法的执行时间少得多,但实际上我得到了相反的结果。

UserService.java

package sample.data;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

@Service

public class UserService {

@Autowired

UserRepository userRepository;

public User save(User user) {

return userRepository.save(user);

}

}

UserRepository.java

package sample.data;

import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.stereotype.Repository;

@Repository

public interface UserRepository extends JpaRepository { }

应用程序

package sample;

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.CommandLineRunner;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.annotation.Bean;

import org.springframework.transaction.annotation.Transactional;

import sample.data.User;

import sample.data.UserService;

import javax.persistence.EntityManager;

import javax.persistence.PersistenceContext;

@SpringBootApplication

@EnableJpaRepositories(considerNestedRepositories = true)

public class Application {

public static void main(String[] args) {

SpringApplication.run(Application.class, args);

}

@Autowired

private UserService userService;

@PersistenceContext

EntityManager entityManager;

@Bean

public CommandLineRunner addUsers() {

return new CommandLineRunner() {

@Transactional

public void run(String... args) throws Exception {

long incoming = System.currentTimeMillis();

for (int i = 1; i <= 10000; i++) {

userService.save(new User("name_" + i));

if (i % 100 == 0) {

entityManager.flush();

entityManager.clear();

}

}

entityManager.close();

System.out.println("Time:" + (System.currentTimeMillis() - incoming));

}

};

}

}

更新的问题。 2000毫秒-1000毫秒= 1000毫秒差异

为什么您期望显式flush()更快?

使用定期冲洗时,应以这种方式更快地工作。 问题应该出在事务的配置中,但是在哪里?

为了对最佳设置进行适当的基准测试,我建议您编写几个循环:增加对象数(最多1mio),增加批大小(最多10k)。 同样,每次设置至少应执行10次,然后再进行测量。 我对微基准测试的经验是,第一个测试用例(或运行)将比第二个测试用例(预热)花费更长的时间。 这可能是测试第一个比第二个慢的原因。 同样,批量大小100可能太低,导致开销引起的速度降低。 只有进行正确的设置,您才能获得重要的基准。

确保在持久性提供程序配置中启用JDBC批处理。如果您使用的是Hibernate,请将其添加到Spring属性中:

spring.jpa.properties.hibernate.jdbc.batch_size=20   // or some other reasonable value

如果不启用批处理,我想性能下降是由于每100个实体清除持久性上下文的开销而引起的,但是我不确定(必须测量)。

更新:

实际上,启用或禁用JDBC批处理不会影响以下事实:偶尔执行flush()不会比没有它快。您使用手册flush()控制的不是刷新的方式(通过批处理语句或单一插入),而是控制何时刷新数据库。

因此,您要比较的是以下内容:

对于每100个对象flush():在刷新时,您将100个实例插入数据库中,然后执行10000/100 = 100次。

如果不使用flush():您只需在内存中收集上下文中的所有10000个对象,并在提交事务时进行10000次插入。

JDBC批处理另一方面会影响刷新的发生方式,但是使用flush()与不使用flush()发出的语句数量仍然相同。

循环中每隔一段时间刷新和清除一次的好处是,避免由于缓存中包含太多对象而可能导致的OutOfMemoryError。

如果尝试更改批处理大小的值还是增加要保存的对象数,是否一样?

当我增加要保存的对象数时,差异会变小。当我增加批次大小的值时,"较小"差异也会增加百分比。

@Drakonoved您是否尝试启用SQL日志记录(stackoverflow.com/questions/30118683/)?我认为每隔一段时间flushing就是为了避免内存问题,而不是为了提高执行时间。

编写微型基准测试非常困难,Aleksey Shipilev在他的" JMH vs Caliper:参考线程"帖子中对此进行了很好的说明。您的案例并不完全是一个微观基准,而是:

如果重复次数少于10,000,将无法让JVM进行预热并将JIT代码设置为默认设置。在评估代码性能之前,请预热JVM。

System.nanoTime()不是System.currentTimeMillis(),用于测量经过时间。如果在ms中进行测量,则结果会因System.currentTimeMillis()中的时钟漂移而产生偏差。

您很可能希望在数据库端进行度量以查明瓶颈。没有瓶颈,很难理解根本原因是什么,例如您的数据库可能位于大西洋的另一端,网络连接成本将使INSERT报表成本蒙上阴影。

您的基准测试是否足够隔离?如果数据库由多个用户和连接共享,则除了基准测试外,其性能也会有所不同。

在当前设置中找到瓶颈,对如何进行验证进行假设,更改基准以匹配该假设,然后再次进行测量以确认。这是找出答案的唯一方法。

在本地计算机上进行应用程序,数据库和度量。要找到瓶颈是问题。

@Drakonoved这不是理想的设置,进行测试的客户端不应与数据库竞争系统资源。您应该检查是否有足够的可用资源来在一台计算机上同时运行这两种资源。

免费资源绰绰有余。这是开发人员的机器。该项目被认为是一个边值问题,但是没有用。

@Drakonoved您正在使用什么数据库?

我正在使用PostgreSQL

您能否解释一下您为什么相信:

Expected result is that execution time with enabled flush() method is much less then with disabled one

在我看来,这是一个根本上错误的假设。 没有充分的理由相信,进行一次10k次微不足道的操作会比不进行冲洗更快速。

只要所有记录都适合内存,我希望非中间刷新版本会更快。 是什么表明执行网络IO来访问数据库100次应该比最后执行1次要快?

如果不能更快,那么为什么spring.jpa.properties.hibernate.jdbc.batch_size=20这样的构造会使差异变小?

因为它与冲洗完全正交。批处理大小定义将多少条记录分组到一个INSERT查询中。批大小确定每个插入语句中将包含多少条记录,刷新确定何时将这些插入语句发送到数据库。有关参考,请参阅:stackoverflow.com/questions/6687422/

其他答案未提及的两个方面。除了刷新之外,您还需要清除休眠会话。如果不清除它,它将增长并会影响您的内存消耗,这可能会导致性能下降。

持久化实体时还需确保ID生成器使用hilosequence。如果您的ID是1,2,3,4,5 .....每个插入将有额外的往返次数以增加ID。

根据这个问题,我们不应该手动管理休眠会话。相反,我做entityManager.clear();

@Drakonoved performancewise这是一个很大的。原因是因为如果您有10,000条记录,则ID生成器需要调用负责ID生成的数据库序列,该数据库序列的生成次数为10 000次,相当于10 000次网络调用。如果使用@SequenceGenerator(name =" EMP_SEQ",locationSize = 25),请注意分配大小属性。 25表示它将以25递增序列,并将使用两者之间的25个可用数字来分配新创建的IDS。如果分配大小为1,则将为生成的每个ID调用序列。这是疯狂的表现。

@Drakonoved Hilo-序列是一种很好的id生成策略,但是您可以使用分配大小合适的序列。还有一件事,由于本地主机的延迟很短,您可能不会注意到分配大小为1的localhost上插入速度的差异如此之大,但是将数据库远程放置并查看会发生什么。

在两种情况下,SequenceGenerator和allocationSize保持相同。

@Drakonoved保持不变?不确定我抓到你了。

我的意思是当我启用或禁用flush()时它们保持不变

ID生成和刷新是无关的两个独立的事物。而且我仍然不明白您所说的"保持不变"是什么意思:)但不要将它们放在相同的上下文中。

"保持不变"是指我不更改它们。

持久保存实体最昂贵的部分是写入数据库。相比之下,将实体保留在JPA中所花费的时间是微不足道的,因为它是纯内存操作。与内存相比,它是IO。

写入数据库也可能会产生相当大的静态开销,这意味着写入数据库的次数可能会影响执行时间。调用EntityManager#flush时,您指示Hibernate将所有未决的更改写入数据库。

因此,您正在做的是将执行100次数据库写入与执行一次数据库写入进行比较。由于IO的开销,前者的速度会大大降低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值