Grails,使用 withTransaction 插入大量数据会导致 OutOfMemoryError
Posted
技术标签:
【中文标题】Grails,使用 withTransaction 插入大量数据会导致 OutOfMemoryError【英文标题】:Grails, Inserting lots of data using withTransaction results in OutOfMemoryError 【发布时间】:2010-09-30 16:55:46 【问题描述】:我正在使用 Grails 1.1 beta2。我需要将大量数据导入我的 Grails 应用程序。如果我反复实例化一个 grails 域类然后保存它,性能会慢得无法接受。以从电话簿中导入人员为例:
for (each person in legacy phone book)
// Construct new Grails domain class from legacy phone book person
Person person = new Person(...)
person.save()
事实证明这非常缓慢。 Grails 邮件列表中的某个人建议在事务中批量保存。所以现在我有:
List batch = new ArrayList()
for (each person in legacy phone book)
// Construct new Grails domain class from legacy phone book person
Person person = new Person(...)
batch.add(person)
if (batch.size() > 500)
Person.withTransaction
for (Person p: batch)
p.save()
batch.clear()
// Save any remaining
for (Person p: batch)
p.save()
这项工作必须更快,至少在最初是这样。每笔交易保存 500 条记录。随着时间的推移,交易需要的时间越来越长。前几笔交易大约需要 5 秒钟,然后它就从那里开始。在大约 100 笔交易之后,每笔交易都需要一分钟以上,这再次令人无法接受。更糟糕的是,Grails 最终会耗尽 Java 堆内存。我可以增加 JVM 堆大小,但这只会延迟 OutOfMemoryError
异常。
有什么想法吗?这就像有一些内部资源没有被释放。性能越来越差,内存被占用,最终系统内存不足。
根据Grails documentation,withTransaction
将闭包传递给Spring的TransactionStatus
对象。我在 TransactionStatus
中找不到任何东西来关闭/结束交易。
编辑:我从 Grails 的控制台 (grails console
) 运行它
编辑:这是内存不足异常:
Exception thrown: Java heap space
java.lang.OutOfMemoryError: Java heap space
at org.hibernate.util.IdentityMap.entryArray(IdentityMap.java:194)
at org.hibernate.util.IdentityMap.concurrentEntries(IdentityMap.java:59)
at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:113)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:65)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:655)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:732)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:701)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)
【问题讨论】:
执行的上下文是什么?石英工作?控制器?当我们过去使用控制器完成此操作时,我们可以设置一个可以限制批处理大小的循环,服务中后续事务的大小也相应限制 【参考方案1】:Ted Naleid 写了一个great blog entry 关于提高批处理性能。包括这里作为参考。
【讨论】:
【参考方案2】:这是所有休眠应用程序的常见问题,它是由休眠会话的增长引起的。我猜测 grails 控制台会为您打开一个休眠会话,其方式类似于我知道它用于正常 Web 请求的“视图中打开会话”模式。
解决方案是获取当前会话并在每批之后将其清除。我不确定您如何使用控制台获取 spring bean,通常对于控制器或服务,您只需将它们声明为成员。然后您可以使用sessionFactory.getCurrentSession()
获取当前会话。为了清除它,只需调用session.clear()
,或者如果您有选择性地使用session.evict(Object)
来处理每个Person
对象。
对于控制器/服务:
class FooController
def sessionFactory
def doStuff =
List batch = new ArrayList()
for (each person in legacy phone book)
// Construct new Grails domain class from legacy phone book person
Person person = new Person(...)
batch.add(person)
if (batch.size() > 500)
Person.withTransaction
for (Person p: batch)
p.save()
batch.clear()
// clear session here.
sessionFactory.getCurrentSession().clear();
// Save any remaining
for (Person p: batch)
p.save()
希望这会有所帮助。
【讨论】:
我会在循环的第 N 次迭代中使用 session.clear() 改进该代码,而不是每次迭代。 完全同意...现在查看该代码我不确定它是否有效。以上是关于Grails,使用 withTransaction 插入大量数据会导致 OutOfMemoryError的主要内容,如果未能解决你的问题,请参考以下文章
使用 JPA.withTransaction 的测试 Actor(java.lang.IllegalStateException:EntityManager 在测试时关闭)
Mongoose 将数据从 withTransaction 助手中传递出去
Mongoose withTransaction 仅部分执行
升级 Play 到 2.4,Slick 到 3.1.1,值 withTransaction 不是 play.api.db.slick.Database 的成员