使用flush()方法对每100行10 000个慢速事务进行处理

Dra*_*ved 13 java flush entitymanager spring-data-jpa spring-boot

我有一个示例项目使用spring-bootwith spring-data-jpapostgres db一个表.

我正在尝试将INSERT10 000条记录循环到表中并测量执行时间 - 为每100条记录启用或禁用类的flush()方法EntityManager.

预期的结果是启用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

UserRepository.java

package sample.data;

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

@Repository
public interface UserRepository extends JpaRepository<User, Long> { }
Run Code Online (Sandbox Code Playgroud)

Application.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));
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

man*_*uti 7

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

spring.jpa.properties.hibernate.jdbc.batch_size=20   // or some other reasonable value
Run Code Online (Sandbox Code Playgroud)

在没有启用批处理的情况下,我认为性能回归是由于每100个实体清除持久性上下文的开销,但我不确定(你必须测量).

更新:

实际上,启用JDBC批处理或禁用它不会影响以下事实:flush()每隔一段时间完成一次并不会比没有它更快.什么你使用手动控制flush()是不如何冲洗完成(通过批处理的语句或单一刀片),而是你控制冲洗到数据库将完成.

所以你要比较的是:

  1. 随着flush()每100个对象:您将100个实例为在冲洗的数据库,你这样做百分之万= 100倍.
  2. 没有flush():您只需在内存中的上下文中收集所有10000个对象,并在提交事务时执行10000次插入.

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

在循环中每隔一段时间刷新和清除的好处是避免OutOfMemoryError由于缓存容纳太多对象而导致的可能性.


Kar*_*cki 3

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

  1. 低于 10,000 次重复不会让 JVM 在默认设置上预热并 JIT 代码。在测量代码性能之前先预热 JVM。

  2. System.nanoTime()System.currentTimeMillis()用于测量经过的时间。如果您正在测量,ms您的结果将因时钟漂移而产生偏差System.currentTimeMillis()

  3. 您很可能希望在数据库端对此进行测量以查明瓶颈。如果没有瓶颈,就很难理解根本原因是什么,例如您的数据库可能位于大西洋的另一边,并且网络连接成本将掩盖INSERT语句成本。

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

找到当前设置中的瓶颈,对如何验证它做出假设,更改基准以匹配假设,然后再次测量以确认。这是解决问题的唯一方法。