在jpa的使用过程中,发现用jpa内置的deleteAll()方法和saveAll()方法,效率都有所不足。看了下它调用的sql语句,发现删除是根据id一条条的删除,批量保存也是逐条先查后存,感觉明显是这个影响了运行速度。

springboot jpa 批量修改 jpa批量更新_java

根据id逐条删除

若是部分批量删除还可以理解,但是当想要整表数据删除时,就显得效率不足。而且delete后,数据库中的空间不会得到释放,后续查询也还是性能较差。

springboot jpa 批量修改 jpa批量更新_jpa_02

逐条保存且每次都要做一个查询

网上看了看有没有大佬有合适的优化方法,大致还是通过原生sql的形式优化处理。

@Repository
public interface AllDeviceRepository extends JpaRepository<AllDeviceDO,Integer> {
    @Transactional
    @Modifying
    @Query(value = "truncate table all_device_info",nativeQuery = true)
    void truncateTable();
}

删除效率有明显提升。

springboot jpa 批量修改 jpa批量更新_数据库_03

可以看到直接执行这条原生sql,速度明显提升,且释放了空间。注意:truncate table有因为权限或锁竞争的风险而导致一直堵塞住运行不下去。此时可以尝试换delete table的原生sql。

然后就是批量新增。自增主键id的策略模式会导致先查询后插入的情况,默认关闭批量操作。所以如果也无需求对表id没有什么要求的话,使用uuid的主键帮助会更大。

@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseDO {
    @GenericGenerator(name = "id-generator", strategy = "uuid")
    @GeneratedValue(generator = "id-generator")
    protected String pid;
    @CreatedDate
    private Date createTime;
    @LastModifiedDate
    private Date updateTime;
}

对于批量操作也可以做一些配置,直接开启批量。

spring.jpa.properties.hibernate.jdbc.batch_size=500
spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true

修改后再运行一遍,可以发现不会在进行先查后插的形式进行批量插入了。

springboot jpa 批量修改 jpa批量更新_hibernate_04

当然,插入前查询对一些业务场景也是很有帮助的。这里主要针对的是追求批量新增时的效率问题,提高性能。

=================================分割线===================================

更新一下,上面的方式修改后,效率有所提升,但是实际底层依旧会有一个查询判断的操作,而且select for update容易造成死锁问题。所以最好还是使用HQL的方式,自己写一个批量操作,首先就可以降低死锁发生的概率,然后还可以避免查询提升效率。

这是针对数据量大的情况,对于事务回滚、数据可靠的要求要低于追求效率。而且 数据量大的情况下,多线程批量操作造成的死锁的概率更大(这里指使用jpa内置的save和delete方法)。所以针对业务场景,可以考虑HQL手写sql。

希望死锁问题的坑对大家有帮助。此处针对jpa,换一个orm框架,比如mybatis,也许效果会更好,避免此类问题产生。

附上postgre数据库死锁处理方法:

select * from pg_stat_activity where datname = '数据库名称'

# 用这个sql可以找出,当前连接执行的sql语句,里面可以看到死锁状态的sql语句以及对应pid。

select pg_cancel_backend('pid');

# 用这个sql可以关闭对应死锁状态的pid任务

select pg_terminate_backend ('pid')

# 用这个可以关闭idle in transaction状态的pid任务