【发布时间】:2015-03-17 17:26:52
【问题描述】:
我们会将大量数据(单一类型的实体)从 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 是一个字符串列表。我们创建了两个 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 分配给保证唯一的客户实体。
这是我们的 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 没有经验,这里是我的问题:
- 对于像我们这样的多线程任务,hibernate.cfg.xml 应该是什么样的?我上面给出的内容是否适合这样的任务,还是有什么错误/遗漏的地方?
- 恰好有 50 个线程,每个线程执行以下操作:首先从 DynamoDB 读取,然后将结果插入 mysql db,然后从 dynamo 读取,依此类推。因此,与 hibernate 通信的正常运行时间不是 100%。在这种情况下,您建议设置 c3p0 连接池大小的 min_size 和 max_size 什么?为了能够理解这个概念,我是否还应该在 hibernate.cfg.xml 中设置剩余的 c3p0 相关标签?
- 如何最大限度地提高批量插入的速度?
注意1我没有写所有属性,因为除了用户列表之外的其余属性都是int,boolean,String等。
注意 2 所有点都经过测试,对性能没有负面影响。当我们不向 mysql db 中插入任何内容时,读取速度会保持稳定数小时。
注意 3 任何关于 mysql 表结构、配置设置、会话/事务、连接池数量、批处理大小等的建议/指导都会非常有帮助!
【问题讨论】:
-
你能把hibernate在插入一个实体时执行的实际SQL贴出来吗?
-
如果您对自己的数据完整性有信心,可以尝试在此批量插入期间关闭外键检查,看看这是否有助于提高性能。
-
您可能需要考虑为每个批次(您刷新/清除的地方)提交/开始您的事务。少量的中等规模的交易通常比少量的大量交易或许多微小的交易要好。
-
如果您在加载数据时无需进行数据完整性检查就可以逃脱,您可能会删除(或不创建)您的索引(主键、外键),直到加载完所有数据。索引维护增加了一堆开销。
-
如果您不需要实体上的 Java 或 Hibernate 业务逻辑,移动数据的最快方法是使用数据创建一个文本文件并使用数据库的本机批量加载进行批量加载工具。