Grails, 使用 withTransaction 插入大量数据会导致 OutOfMemoryError

2022-09-04 07:30:55

我使用的是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的文档,将闭包传递给Spring的对象。我找不到任何东西来关闭/结束交易。withTransactionTransactionStatusTransactionStatus

编辑:我正在从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写了一篇关于提高批处理性能的精彩博客文章。包括在这里作为参考。


答案 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()
        }
    }
}

希望这有帮助。


推荐