我试图使用Hibernate(JPA)在5秒内在MYSQL表中插入100,000行。我已经尝试了冬眠提供的所有技巧,但仍然不能超过 35 秒。
第一次优化:我从 IDENTITY 序列生成器开始,这导致插入时间为 60 秒。后来我放弃了序列生成器,开始通过读取MAX(id)
并使用AtomicInteger.incrementAndGet()
自己分配字段来自己分配@Id
字段。这将插入时间减少到 35 秒。
第二次优化:我启用了批量插入,通过添加
<prop key="hibernate.jdbc.batch_size">30</prop>
<prop key="hibernate.order_inserts">true</prop>
<prop key="hibernate.current_session_context_class">thread</prop>
<prop key="hibernate.jdbc.batch_versioned_data">true</prop>
到配置。我震惊地发现,批量插入对减少插入时间没有任何作用。还是35秒!
现在,我正在考虑尝试使用多个线程进行插入。 有人有什么指示吗?我应该选择MongoDB吗?
以下是我的配置: 1. 休眠配置 `
<bean id="entityManagerFactoryBean" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.progresssoft.manishkr" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.jdbc.batch_size">30</prop>
<prop key="hibernate.order_inserts">true</prop>
<prop key="hibernate.current_session_context_class">thread</prop>
<prop key="hibernate.jdbc.batch_versioned_data">true</prop>
</props>
</property>
</bean>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"
id="dataSource">
<property name="driverClassName" value="${database.driver}"></property>
<property name="url" value="${database.url}"></property>
<property name="username" value="${database.username}"></property>
<property name="password" value="${database.password}"></property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactoryBean" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
'
- >实体配置:
'
@Entity
@Table(name = "myEntity")
public class MyEntity {
@Id
private Integer id;
@Column(name = "deal_id")
private String dealId;
....
....
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "timestamp")
private Date timestamp;
@Column(name = "amount")
private BigDecimal amount;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "source_file")
private MyFile sourceFile;
public Deal(Integer id,String dealId, ....., Timestamp timestamp, BigDecimal amount, SourceFile sourceFile) {
this.id = id;
this.dealId = dealId;
...
...
...
this.amount = amount;
this.sourceFile = sourceFile;
}
public String getDealId() {
return dealId;
}
public void setDealId(String dealId) {
this.dealId = dealId;
}
...
...
....
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
....
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
'
- 持久化代码(服务):
'
@Service
@Transactional
public class ServiceImpl implements MyService{
@Autowired
private MyDao dao;
....
`void foo(){
for(MyObject d : listOfObjects_100000){
dao.persist(d);
}
}
' 4.道类:
'
@Repository
public class DaoImpl implements MyDao{
@PersistenceContext
private EntityManager em;
public void persist(Deal deal){
em.persist(deal);
}
}
'
原木: `
DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:32.906 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:32.906 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:32.906 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:32.906 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:32.906 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:32.906 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:32.906 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:32.906 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:32.906 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:32.906 [http-nio-8080-exec-2]
。 ...
DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:34.002 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:34.002 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:34.002 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:34.002 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:34.002 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:34.002 [http-nio-8080-exec-2] DEBUG o.h.e.j.b.internal.AbstractBatchImpl - Reusing batch statement
18:26:34.002 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - insert into deal (amount, deal_id, timestamp, from_currency, source_file, to_currency, id) values (?, ?, ?, ?, ?, ?, ?)
18:26:34.002 [http-nio-8080-exec-2] DEBUG o.h.e.j.batch.internal.BatchingBatch - Executing batch size: 27
18:26:34.011 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL - update deal_source_file set invalid_rows=?, source_file=?, valid_rows=? where id=?
18:26:34.015 [http-nio-8080-exec-2] DEBUG o.h.e.j.batch.internal.BatchingBatch - Executing batch size: 1
18:26:34.018 [http-nio-8080-exec-2] DEBUG o.h.e.t.i.jdbc.JdbcTransaction - committed JDBC Connection
18:26:34.018 [http-nio-8080-exec-2] DEBUG o.h.e.t.i.jdbc.JdbcTransaction - re-enabling autocommit
18:26:34.032 [http-nio-8080-exec-2] DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@2354fb09] after transaction
18:26:34.032 [http-nio-8080-exec-2] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
18:26:34.032 [http-nio-8080-exec-2] DEBUG o.h.e.j.internal.JdbcCoordinatorImpl - HHH000420: Closing un-released batch
18:26:34.032 [http-nio-8080-exec-2] DEBUG o.h.e.j.i.LogicalConnectionImpl - Releasing JDBC connection
18:26:34.033 [http-nio-8080-exec-2] DEBUG o.h.e.j.i.LogicalConnectionImpl - Released JDBC connection
'
在尝试了所有可能的解决方案后,我终于找到了在 100,000 秒内插入 5 行的解决方案!
我尝试过的事情:
1) 使用 AtomicInteger 将休眠/数据库的自动增量/生成的 ID 替换为自生成 ID
2) 启用 batch_size=50 的batch_inserts
3) 在每次"batch_size"次持久()调用后刷新缓存
4)多线程(没有尝试这个)
最后,有效的方法是使用本机多插入查询并在一个 sql 插入查询中插入 1000 行,而不是在每个实体上使用persist()。为了插入 100,000 个实体,我创建了一个本机查询,如下所示"INSERT into MyTable VALUES (x,x,x),(x,x,x).......(x,x,x)"
[在一个 sql 插入查询中插入 1000 行]
现在插入 3 条记录大约需要 100,000 秒!所以瓶颈是orm本身!对于批量插入,似乎唯一有效的是本机插入查询!
-
你正在使用 Spring 来管理事务,但通过使用
thread
作为当前会话上下文来打破它。使用Spring管理您的交易时,不要弄乱hibernate.current_session_context_class
属性。删除它。 -
不要使用
DriverManagerDataSource
使用像HikariCP这样的适当连接池。 -
在 for 循环中,您应该定期
flush
和clear
EntityManager
,最好与批大小相同。如果你不做一个持久,则需要更长的时间,因为当你这样做时,Hibernate会检查第一级缓存中的脏对象,对象越多,花费的时间就越多。对于 10 或 100 个是可以接受的,但为每个持久检查 10000 个对象会付出代价。
-
@Service
@Transactional
public class ServiceImpl implements MyService{
@Autowired
private MyDao dao;
@PersistenceContext
private EntityManager em;
void foo(){
int count = 0;
for(MyObject d : listOfObjects_100000){
dao.persist(d);
count++;
if ( (count % 30) == 0) {
em.flush();
em.clear();
}
}
}
有关更深入的说明,请参阅此博客和此博客。
另一个要考虑的选项是 StatelessSession:
面向命令的 API,用于对 数据库。
无状态会话不实现一级缓存,也不实现 与任何二级缓存交互,也不实现 事务性后写或自动脏检查,也不执行 操作级联到关联的实例。集合被忽略 无状态会话。通过无状态会话执行的操作 绕过 Hibernate 的事件模型和拦截器。无状态会话 容易受到数据混叠效应的影响,因为缺少 一级缓存。
对于某些类型的事务,无状态会话可以执行 比有状态会话稍快。
相关讨论: 使用无状态会话进行批处理
Uff。 你可以做很多事情来提高速度。
1.) 使用 @DynamicInsert 和 @DynamicUpdate 防止数据库插入非空列和更新更改的列。
2.) 尝试将列直接插入(不使用休眠)到数据库中,看看休眠是否真的是你的瓶颈。
3.) 使用会话工厂,并且每 100 次插入仅提交一次事务。或者只打开和关闭事务一次,每 100 次插入刷新一次数据。
4.) 使用 ID 生成策略"序列"并让休眠预分配(通过参数分配大小)ID 。
5.) 使用缓存。
如果使用不当,其中一些可能的解决方案可能会有时序上的缺点。但是你有很多机会。