Hibernate如何进行批量插入或更新?

31

我需要从每日的CSV文件中消耗相当大量的数据。CSV文件包含约120k条记录。在使用hibernate时,这会减慢速度。基本上,当使用saveOrUpdate()保存或更新每个实例时,似乎hibernate在每个单独的INSERT(或UPDATE)之前都会执行SELECT操作。我可以理解它这样做的原因,但这对于批量处理来说非常低效,我正在寻找替代方案。

我相信性能问题出在我使用hibernate的方式上,因为我用与之前版本完全相同方式解析CSV的本地SQL版本运行速度要快得多。

那么,实际的问题是,在使用hibernate时是否存在类似于mysql的“INSERT ... ON DUPLICATE”语法的替代方法?

或者,如果我选择为此使用本地SQL,那么我可以在hibernate事务中执行本地SQL吗?也就是说,它会支持提交/回滚吗?


“ hibernate is doing a SELECT before every single insert (or update) when using saveOrUpdate()." 这句话是什么意思?你能发一下保存数据的代码吗?顺便说一句,12万条记录是非常庞大的数据量! - Rakesh
刚刚发现了一篇关于hibernate批处理的文章。 - Shailendra
6个回答

43

批量操作可能存在许多潜在的瓶颈。最好的方法取决于您的数据长什么样。请查看Hibernate手册中有关批处理的部分。

最少应确保使用以下模式(从手册中复制):

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
    if ( i % 20 == 0 ) { //20, same as the JDBC batch size
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}

tx.commit();
session.close();

如果你正在将一个扁平文件映射到一个非常复杂的对象图中,你可能需要更有创意,但基本原则是要在每次刷新/提交时找到推送好大小的数据块到数据库和避免爆炸性地增加会话级缓存大小之间取得平衡。

最后,如果你不需要Hibernate处理任何集合或级联以正确插入数据,请考虑使用StatelessSession


我正在刷新和清除我的会话,代码没有内存问题。我有额外的选择问题!:P 我已经阅读了手册,但找不到任何东西。数据非常简单,不需要级联。我只需要摆脱这个任务中被调用120K次的冗余选择:P - JustDanyul
@JustDanyul 这个操作中新实体的大约百分比是多少(即选择中实际上多少是不必要的)?您是否在使用版本控制? - jcwayne
1
实际的百分比会因天而异。但是,实际上没有任何查询应该是必要的。现在大多数数据库(即使是像SQLite这样的“玩具”数据库)都提供了功能,如果数据已经存在,就可以自动更新记录。(不需要先轮询它来查找是否存在 :)) - JustDanyul
1
你说得没错,Hibernate 几乎支持任何你可能使用的数据库功能。但是,据我所知,Hibernate 使得使用该功能变得不可能。如果你不想通过 JDBC 直接绕过 Hibernate 执行 SQL,那么你唯一的选择就是找到其他方法来加速整个导入过程(例如并发、调整连接池设置、禁用二级缓存等)。 - jcwayne
你能为无状态方法添加版本吗? - G. Ciardini

5

我使用以下代码进行更新:

Hibernate批处理

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

ScrollableResults employeeCursor = session.createQuery("FROM EMPLOYEE")
                                   .scroll();
int count = 0;

while ( employeeCursor.next() ) {
   Employee employee = (Employee) employeeCursor.get(0);
   employee.updateEmployee();
   seession.update(employee); 
   if ( ++count % 50 == 0 ) {
      session.flush();
      session.clear();
   }
}
tx.commit();
session.close();

但对于插入,我会选择 jcwayne 的答案


4

3

高吞吐量数据导出

如果您只想导入数据而不进行任何处理或转换,则像PostgreSQL的COPY工具是导入数据的最快方式。

批量处理

然而,如果您需要进行转换、数据聚合、现有数据和新数据之间的相关性/合并等操作,则需要应用程序级别的批处理。

在这种情况下,您需要定期执行flush-clear-commit操作:

int entityCount = 50;
int batchSize = 25;
 
EntityManager entityManager = entityManagerFactory()
    .createEntityManager();
     
EntityTransaction entityTransaction = entityManager
    .getTransaction();
 
try {
    entityTransaction.begin();
 
    for (int i = 0; i < entityCount; i++) {
        if (i > 0 && i % batchSize == 0) {
            entityTransaction.commit();
            entityTransaction.begin();
 
            entityManager.clear();
        }
 
        Post post = new Post(
            String.format("Post %d", i + 1)
        );
         
        entityManager.persist(post);
    }
 
    entityTransaction.commit();
} catch (RuntimeException e) {
    if (entityTransaction.isActive()) {
        entityTransaction.rollback();
    }
    throw e;
} finally {
    entityManager.close();
}

此外,请确保使用以下配置属性启用JDBC批处理:

<property
    name="hibernate.jdbc.batch_size"
    value="25"
/>
 
<property
    name="hibernate.order_inserts"  
    value="true"
/>
 
<property
    name="hibernate.order_updates"  
    value="true"
/>

批量处理

批量处理适用于所有行都符合预定义的过滤条件,因此您可以使用单个UPDATE更改所有记录。

然而,修改数百万条记录的批量更新可能会增加重做日志的大小或在仍使用2PL(两阶段锁定)的数据库系统上占用大量锁,例如SQL Server。

因此,虽然批量更新是更改多个记录的最有效方法,但您必须注意要更改多少记录以避免长时间运行的事务。

此外,您可以将批量更新与乐观锁定相结合,以使其他OLTP事务不会丢失批量处理过程中所做的更新。


1
如果您使用序列或本地生成器,Hibernate 将使用 select 获取 id:
<id name="id" column="ID">
    <generator class="native" />
</id>

您应该使用hilo或seqHiLo生成器:

<id name="id" type="long" column="id">  
    <generator class="seqhilo">
        <param name="sequence">SEQ_NAME</param>
        <param name="max_lo">100</param>
    </generator>
</id>

0

"extra" select是用于生成数据的唯一标识符。

切换到HiLo序列生成器,您可以将序列往返到数据库的次数减少到分配大小的数量。请注意,除非您调整HiLo生成器的序列值,否则主键中会存在间隙。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接