我们将从Amazon的DynamoDB将大量数据(单一类型的实体)迁移到MySQL DB中。我们使用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.
}
用户是字符串列表。我们创建了两个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 我没有编写所有属性,因为除用户列表以外的其余属性都是int,boolean,String等。
注意2 所有点都经过测试,对性能没有负面影响。当我们不向mysql db插入任何内容时,读取速度会保持稳定几个小时。
注3 有关mysql表结构,配置设置,会话/事务,连接池数量,批量大小等的任何建议/指导都会非常有用!
答案 0 :(得分:2)
假设您在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();
答案 1 :(得分:0)
在您的方案中,有25个线程将数据批量插入到一个表中。 MySQL必须维护ACID properties,而一个表中的许多记录的25个事务仍处于打开状态或正在提交。这可能会导致巨大的开销。
从数据库迁移数据时,当与数据库进行许多来回通信时,网络延迟可能会导致严重的延迟。在这种情况下,使用多个线程可能是有益的。但是,在进行批量提取和批量插入时,由于数据库驱动程序将(或应该)在不进行大量来回通信的情况下进行数据通信,因此几乎没有什么好处。
在批处理场景中,从1个线程开始,该线程读取数据,准备批处理并将其放入队列中,用于从准备好的批处理中写入数据的1个线程。保持批量较小(100到1 000条记录)并经常提交(每100条记录左右)。这将最小化维护表的开销。如果网络延迟是一个问题,请尝试使用2个线程进行读取,使用2个进行写入(但任何性能增益可能会被维护2个线程同时使用的表的开销所抵消)。
由于没有生成的ID,您应该受益于hibernate配置中已有的hibernate.jdbc.batch_size选项。 <{1}}选项(将其设置为250左右)也可能是有意义的。
正如@ hermant1900所提到的,使用hibernate.jdbc.fetch_size
也是一个好主意。但到目前为止,@ Rob在评论中提到了最快的方法:使用数据库工具将数据导出到文件import it in MySQL。我非常确定这也是首选方法:它花费的时间更少,处理更少,涉及的变量更少 - 总体来说更可靠。