繁体   English   中英

插入海量数据时出现Hibernate性能问题

[英]Hibernate performance issue while inserting massive data

我们将从Amazon的DynamoDB迁移大量数据(单一类型的实体)到MySQL数据库。 我们使用Hibernate将这个类映射到一个mysql实体。 大约有300万个实体(不包括列属性行) 这是我们的类映射摘要:

@Entity
@Table(name = "CUSTOMER")
public class Customer {
    @Id
    @Column(name = "id")
    private String id;

    //Other properties in which all of them are primitive types/String

    @ElementCollection
    @CollectionTable(name = "CUSTOMER_USER", joinColumns = @JoinColumn(name = "customer_id"))
    @Column(name = "userId")
    private List<String> users;

    // CONSTRUCTORS, GETTERS, SETTERS, etc.
}

users是String列表。 我们创建了两个mysql表,如下所示:

CREATE TABLE CUSTOMER(id VARCHAR(100), PRIMARY KEY(id));
CREATE TABLE CUSTOMER_USER(customer_id VARCHAR(100), userId VARCHAR(100), PRIMARY KEY(customer_id, userId), FOREIGN KEY (customer_id) REFERENCES CUSTOMER(id));

注意:我们不会让hibernate生成任何id值,我们将我们的ID分配给保证唯一的Customer实体。

这是我们的hibernate.cfg.xml:

<hibernate-configuration>    
    <session-factory>    
    <property name="hibernate.dialect">   org.hibernate.dialect.MySQLDialect </property>    
    <property name="hibernate.connection.driver_class"> com.mysql.jdbc.Driver </property>  
    <property name="hibernate.connection.url"> jdbc:mysql://localhost/xxx </property>    
    <property name="hibernate.connection.username"> xxx </property>    
    <property name="hibernate.connection.password"> xxx </property>
    <property name="hibernate.connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
    <property name="hibernate.jdbc.batch_size"> 50 </property>
    <property name="hibernate.cache.use_second_level_cache">false</property>
    <property name="c3p0.min_size">30</property>
    <property name="c3p0.max_size">70</property>
    </session-factory> 
</hibernate-configuration>

我们正在创建一些线程,每个线程从Dynamo读取数据并通过Hibernate将它们插入我们的MySQl DB。 以下是每个线程的作用:

// Each single thread brings resultItems from DynamoDB
Session session = factory.openSession();
Transaction tx = session.beginTransaction();
for(int i = 0; i < resultItems.size(); i++) {
    Customer cust = new Customer(resultItems.get(i));
    session.save(cust);
    if(i % BATCH_SIZE == 0) {
        session.flush();
        session.clear();
    }
}
tx.commit();
session.close();

我们拥有自己的性能监控功能,并且我们不断记录整体读/写性能。 问题是,迁移从读取/写入1500项/秒(平均)开始,但只要CUSTOMER和CUSTOMER_USER表中的行数增加(几分钟后,r / w速度大约为500项/秒)。 我对Hibernate没有经验,这是我的问题:

  1. 对于像我们这样的多线程任务,hibernate.cfg.xml应该是什么样的? 我上面给出的内容是否适合这样的任务,还是有任何错误/缺失点?
  2. 有50个线程,每个线程都遵循:首先从DynamoDB读取,然后将结果插入mysql db,然后从dynamo读取,依此类推。 因此,与休眠通信的正常运行时间不是100%。 在这种情况下,您建议设置c3p0连接池大小的min_size和max_size? 为了能够理解这个概念,我是否还应该在hibernate.cfg.xml中设置剩余的c3p0相关标签?
  3. 如何最大限度地提高批量插入的速度?

注1:我没有编写所有属性,因为除用户列表以外的其余属性都是int,boolean,String等。

注2:所有点都经过测试,对性能没有负面影响。 当我们不向mysql db插入任何内容时,读取速度保持稳定数小时。

注3 :有关mysql表结构,配置设置,会话/事务,连接池数量,批量大小等的任何建议/指导将非常有用!

假设你在hibernate事务中没有做任何其他事情而不仅仅是将数据插入这两个表中,你可以使用StatelessSession session = sessionFactory.openStatelessSession(); 而不是正常会话,这减少了维护缓存的开销。 但是,您必须单独保存嵌套的集合对象。 请参阅https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/batch.html

所以它可能是这样的 -

// Each single thread brings resultItems from DynamoDB
StatelessSession session = factory.openStatelessSession();
Transaction tx = session.beginTransaction();
for(int i = 0; i < resultItems.size(); i++) {
    Customer cust = new Customer(resultItems.get(i));   
    Long id = session.save(cust); // get the generated id
    // TODO: Create a list of related customer users and assign the id to all of them and then save those customer user objects in the same transaction.  
    if(i % BATCH_SIZE == 0) {
        session.flush();
        session.clear();
    }
}
tx.commit();
session.close();

在您的方案中,有25个线程将​​数据批量插入到一个表中。 MySQL必须维护ACID属性,而一个表中的许多记录的25个事务保持打开或正在提交。 这可能会导致巨大的开销。

从数据库迁移数据时,当与数据库进行许多来回通信时,网络延迟可能会导致严重的延迟。 在这种情况下,使用多个线程可能是有益的。 但是,在进行批量提取和批量插入时,由于数据库驱动程序将(或应该)在不进行大量来回通信的情况下进行数据通信,因此几乎无法获得。

在批处理方案中,从1个线程开始,该线程读取数据,准备批处理并将其放入队列中,用于从准备好的批处理中写入数据的1个线程。 保持批量较小(100到1 000条记录)并经常提交(每100条记录左右)。 这将最小化维护表的开销。 如果网络延迟是一个问题,请尝试使用2个线程进行读取,使用2个进行写入(但任何性能增益可能会被维护2个线程同时使用的表的开销所抵消)。

由于没有生成的ID,您应该受益于hibernate配置中已有的hibernate.jdbc.batch_size选项。 hibernate.jdbc.fetch_size选项(将其设置为250左右)也可能是有意义的。

正如@ hermant1900所提到的,使用StatelessSession也是一个好主意。 但到目前为止,@ Rob在评论中提到了最快的方法:使用数据库工具将数据导出到文件并将其导入MySQL 我很确定这也是首选方法:它花费的时间更少,处理更少,涉及的变量更少 - 总体来说更可靠。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM