我有一个示例项目,其中spring-boot
与spring-data-jpa
和postgres db
与一个表一起使用。
我试图将INSERT
中的10000条记录循环到表中并测量执行时间-为每100条记录从flush()
类启用或禁用EntityManager
方法。
预期结果是,启用了flush()
方法的执行时间比禁用了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);
}
}
的执行时间要少得多,但实际上我得到的结果却相反。
UserService.java
package sample.data;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> { }
UserRepository.java
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));
}
};
}
}
Application.java
<user-detail [user]="selectedUser"></user-detail>
答案 0 :(得分:7)
确保在持久性提供程序配置中启用JDBC批处理。如果您使用的是Hibernate,请将其添加到您的Spring属性中:
spring.jpa.properties.hibernate.jdbc.batch_size=20 // or some other reasonable value
没有启用批处理,我想性能下降是由于每100个实体清除持久性上下文的开销而引起的,但是我不确定(您必须测量)。
更新:
实际上,启用或禁用JDBC批处理不会影响以下事实,即偶尔进行flush()
不会比没有批处理更快。您使用手册flush()
控制的不是 如何 (通过批处理语句或单一插入)进行刷新,而是控制< strong> 何时 将刷新到数据库。
因此,您要比较的是以下内容:
flush()
个对象:在刷新时将100个实例插入数据库,然后执行10000/100 = 100次。flush()
:您只需在内存中收集上下文中的所有10000个对象,并在提交事务时进行10000次插入。另一方面,JDBC批处理会影响刷新的发生方式,但是使用flush()
和不使用flush()
发出的语句数量仍然相同。
每隔一段时间刷新和清除一次循环的好处是避免由于缓存中包含太多对象而导致OutOfMemoryError
的可能性。
答案 1 :(得分:3)
写一个微型基准测试很困难,which is greatly illustrated by Aleksey Shipilev in his "JMH vs Caliper: reference thread" post。您的案例不完全是一个微观基准,而是:
低于10,000次重复将不会让JVM进行预热并将JIT代码设置为默认设置。在评估代码性能之前,请先对JVM进行预热。
git fetch
而非git fetch
用于测量经过时间。如果您在git fetch
中进行测量,那么您的结果将因System.nanoTime()
中的时钟漂移而产生偏差。
您很可能希望在数据库端进行度量以查明瓶颈。没有瓶颈,很难理解根本原因是什么,例如您的数据库可能位于大西洋的另一侧,并且网络连接成本将使System.currentTimeMillis()
报表成本蒙上阴影。
您的基准测试是否足够隔离?如果数据库由多个用户和连接共享,则除了基准测试外,其性能也会有所不同。
找到当前设置中的瓶颈,对如何进行验证进行假设,更改基准以匹配该假设,然后再次进行测量以确认。这是找出答案的唯一方法。
答案 2 :(得分:1)
持久保存实体最昂贵的部分是写入数据库。相比之下,将实体保留在JPA中所花费的时间是微不足道的,因为它是纯内存操作。与内存相比,它是IO。
写入数据库可能还会有相当大的静态开销,这意味着您写入数据库的次数可能会影响执行时间。调用EntityManager#flush
时,您指示Hibernate将所有未决的更改写入数据库。
因此,您正在做的是将执行100次数据库写入与执行一次数据库写入进行比较。由于IO的开销,前者的速度会大大降低。
答案 3 :(得分:1)
其他答案未提及的两个方面。除了刷新之外,您还需要清除休眠会话。如果不清除它,它将增长并会影响您的内存消耗,这可能会导致性能下降。
持久化实体时的另一件事是确保您的ID生成器使用hilosequence。如果您的ID为1,2,3,4,5 .....每个插入将有额外的往返次数以增加ID。
答案 4 :(得分:1)
您能解释一下为什么为什么:
预期结果是,启用了flush()方法的执行时间比禁用了一次的执行时间要短
在我看来,这是一个根本上错误的假设。没有充分的理由相信,进行一次10k次微不足道的操作会比不进行冲洗更快速。
只要所有记录都适合内存,我就会期望,非中间刷新版本会更快。是什么表明执行网络IO要访问数据库100次应该比最后一次访问数据库要快1次?