使用 Hibernate 批量插入或更新?

Posted

技术标签:

【中文标题】使用 Hibernate 批量插入或更新?【英文标题】:Bulk insert or update with Hibernate? 【发布时间】:2011-11-13 01:11:14 【问题描述】:

我需要从每日 CSV 文件中获取大量数据。 CSV 包含大约 120K 条记录。使用休眠时,这会减慢速度。基本上,当使用 saveOrUpdate(); 时,似乎hibernate 在每次插入(或更新)之前都在执行 SELECT;对于使用 saveOrUpdate() 持久化的每个实例,在实际 INSERT 或 UPDATE 之前发出 SELECT。我可以理解它为什么要这样做,但是它在进行批量处理时效率非常低,我正在寻找替代方案

我确信性能问题在于我为此使用 hibernate 的方式,因为我得到了另一个使用本机 SQL 的版本(以 excat 相同的方式解析 CSV)及其字面上围绕这个运行的圆圈新版本)

那么,对于实际问题,是否存在 mysqls "INSERT ... ON DUPLICATE" 语法的休眠替代方案?

或者,如果我选择为此执行本机 SQL,我可以在休眠事务中执行本机 SQL 吗?意思是,它会支持提交/回滚吗?

【问题讨论】:

当使用 saveOrUpdate() 时,“hibernate 在每次插入(或更新)之前执行 SELECT”是什么意思。 ?你能发布你用来保存数据的代码吗?顺便说一句,120k 记录是一个巨大的数据! 刚找到一篇关于batch processing in hibernate的文章 【参考方案1】:

批量操作存在许多可能的瓶颈。最好的方法很大程度上取决于您的数据是什么样的。查看Hibernate Manual 批处理部分。

至少,请确保您使用的是以下模式(从手册中复制):

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 此操作中新实体的大致百分比是多少(即实际上不需要的选择百分比是多少)?你在使用版本控制吗? 实际百分比每天都会变化。但是,没有一个选择真的是必需的。今天的大多数数据库(甚至是像 SQLite 这样的“玩具”数据库)都提供了可以让您在数据已经存在时自动更新记录的功能。 (不必先轮询它,看看它是否存在:)) 您是正确的,该功能几乎存在于您可能与 Hibernate 一起使用的任何数据库中。但是,尽我所能告诉 Hibernate 使使用它变得不可能。如果您不想绕过 Hibernate 并直接通过 JDBC 执行 SQL,您唯一的选择是寻找其他方法来加快您的整体导入过程(例如并发、调整连接池设置、禁用 2 级缓存等) . 可以添加无状态方法的版本吗?【参考方案2】:

来自Hibernate Batch Processing 对于更新,我使用了以下内容:

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 答案

【讨论】:

【参考方案3】:

根据an answer to a similar question,它可以由configuring Hibernate to insert objects using a custom stored procedure 完成,它使用您数据库的upsert 功能。不过,它并不漂亮。

【讨论】:

【参考方案4】:

高吞吐量数据导出

如果您只想导入数据而不做任何处理或转换,那么像 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"
/>

批量处理

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

但是,使用修改数百万条记录的批量更新可能会增加重做日志的大小,或者最终在仍然使用 2PL (Two-Phase Locking) 的数据库系统(如 SQL Server)上占用大量锁。

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

此外,您可以将批量更新与乐观锁定结合起来,这样其他 OLTP 事务就不会丢失批量处理过程完成的更新。

【讨论】:

【参考方案5】:

如果您使用序列或原生生成器,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>

【讨论】:

【参考方案6】:

“额外”选择是为您的数据生成唯一标识符。

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

【讨论】:

以上是关于使用 Hibernate 批量插入或更新?的主要内容,如果未能解决你的问题,请参考以下文章

Informix 数据库中的 Hibernate 批量插入(获取 sql 日志跟踪)

Slick 3.0 批量插入或更新(更新插入)

批量插入中的 Postgres 错误:关系“hibernate_sequence”不存在位置 17

JPA/Hibernate 批量(批量)插入

Hibernate/MySQL 批量插入问题

使用 Petapoco 批量插入/更新