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 的成员

Django 嵌套事务 - “with transaction.atomic()”

如何使用 grails 命令行编译 grails 项目?