Using flush() method on each 100 rows of 10 000 slows transaction
我有一个示例项目,其中将
我试图将循环中的
预期的结果是,启用了
UserService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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
1 2 3 4 5 6 7 | package sample.data; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository<User, Long> { } |
应用程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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)); } }; } } |
确保在持久性提供程序配置中启用JDBC批处理。如果您使用的是Hibernate,请将其添加到Spring属性中:
1 | spring.jpa.properties.hibernate.jdbc.batch_size=20 // or some other reasonable value |
如果不启用批处理,我想性能下降是由于每100个实体清除持久性上下文的开销而引起的,但是我不确定(必须测量)。
更新:
实际上,启用或禁用JDBC批处理不会影响以下事实:偶尔执行
因此,您要比较的是以下内容:
JDBC批处理另一方面会影响刷新的发生方式,但是使用
循环中每隔一段时间刷新和清除一次的好处是,避免由于缓存中包含太多对象而可能导致的
编写微型基准测试非常困难,Aleksey Shipilev在他的" JMH vs Caliper:参考线程"帖子中对此进行了很好的说明。您的案例并不完全是一个微观基准,而是:
如果重复次数少于10,000,将无法让JVM进行预热并将JIT代码设置为默认设置。在评估代码性能之前,请预热JVM。
您很可能希望在数据库端进行度量以查明瓶颈。没有瓶颈,很难理解根本原因是什么,例如您的数据库可能位于大西洋的另一端,网络连接成本将使
您的基准测试是否足够隔离?如果数据库由多个用户和连接共享,则除了基准测试外,其性能也会有所不同。
在当前设置中找到瓶颈,对如何进行验证进行假设,更改基准以匹配该假设,然后再次进行测量以确认。这是找出答案的唯一方法。
您能否解释一下您为什么相信:
Expected result is that execution time with enabled flush() method is much less then with disabled one
在我看来,这是一个根本上错误的假设。 没有充分的理由相信,进行一次10k次微不足道的操作会比不进行冲洗更快速。
只要所有记录都适合内存,我希望非中间刷新版本会更快。 是什么表明执行网络IO来访问数据库100次应该比最后执行1次要快?
其他答案未提及的两个方面。除了刷新之外,您还需要清除休眠会话。如果不清除它,它将增长并会影响您的内存消耗,这可能会导致性能下降。
持久化实体时还需确保ID生成器使用hilosequence。如果您的ID是1,2,3,4,5 .....每个插入将有额外的往返次数以增加ID。
持久保存实体最昂贵的部分是写入数据库。相比之下,将实体保留在JPA中所花费的时间是微不足道的,因为它是纯内存操作。与内存相比,它是IO。
写入数据库也可能会产生相当大的静态开销,这意味着写入数据库的次数可能会影响执行时间。调用
因此,您正在做的是将执行100次数据库写入与执行一次数据库写入进行比较。由于IO的开销,前者的速度会大大降低。