Hibernate:遍历数百万行并且不泄漏内存

Posted

技术标签:

【中文标题】Hibernate:遍历数百万行并且不泄漏内存【英文标题】:Hibernate: Walk millions of rows and don't leak memory 【发布时间】:2011-03-05 12:29:46 【问题描述】:

下面的代码可以运行,但是 Hibernate 永远不会放开它对任何对象的控制。调用session.clear() 会导致有关获取连接类的异常,并且在检索下一个对象之前调用session.evict(currentObject) 也无法释放内存。最终我耗尽了我的堆空间。

检查我的堆转储,StatefulPersistenceContext 是指向我的对象的所有引用的垃圾收集器的根。

public class CriteriaReportSource implements JRDataSource 

    private ScrollableResults sr;
    private Object currentObject;
    private Criteria c;
    private static final int scrollSize = 10;
    private int offset = 1;

    public CriteriaReportSource(Criteria c) 
        this.c = c;
        advanceScroll();
    

    private void advanceScroll() 
//        ((Session) Main.em.getDelegate()).clear();
        this.sr = c.setFirstResult(offset)
                   .setMaxResults(scrollSize)
                   .scroll(ScrollMode.FORWARD_ONLY);
        offset += scrollSize;
    

    public boolean next() 
        if (sr.next()) 
            currentObject = sr.get(0);
            if (sr.isLast()) 
                advanceScroll();
            
            return true;
        

        return false;
    

    public Object getFieldValue(JRField jrf) throws JRException 
        Object retVal = null;
        if(currentObject == null)  return null; 
        try 
            retVal = PropertyUtils.getProperty(currentObject, jrf.getName());
         catch (Exception ex) 
            Logger.getLogger(CriteriaReportSource.class.getName()).log(Level.SEVERE, null, ex);
        
        return retVal;
    

【问题讨论】:

我在Hibernate reference 中看到了这样的例子,但是,session.flush()session.clear() 之前被调用。你能试试它是否有影响吗? 它是什么数据库?并非所有都支持真正的光标滚动。另外,我没有看到您关闭 ScrollableResults。 关于flush before clear问题,没有效果。由于我没有更新数据库,因此没有任何意义。如果我正在更新,它会阻止我在清除时倾倒所有内容。 (我确实对此进行了测试以回答您的问题。) 查看结果集部分 - 可能是 mysql JDBC 驱动程序将其全部加载到内存中:dev.mysql.com/doc/refman/5.0/en/…。另请查看 useCursorFetch。以下是更多信息(另请参阅 cmets):benjchristensen.com/2008/05/27/… 【参考方案1】:

不要在此处使用有状态会话,它不是遍历数百万行和构建报告的正确工具。请改用The StatelessSession interface。

如果使用 MySQL Connector/J 还不够,您还需要使用 this 来破坏 JDBC 驱动程序完成的内部缓冲:

Query query = session.createQuery(query);
query.setReadOnly(true);
// MIN_VALUE gives hint to JDBC driver to stream results
query.setFetchSize(Integer.MIN_VALUE);
ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
// iterate over results
while (results.next()) 
    Object row = results.get();
    // process row then release reference
    // you may need to evict() as well

results.close();

【讨论】:

目前,使用 StatelessSession 解决了内存问题,但是当访问关联的 OneToMany 连接时(延迟加载)会引发所有问题。一种有趣的输赢。如果急切加载解决了这个问题,我在进行测试时有什么建议吗? 原来 StatelessSession 无法处理集合。我确实找到了一种使用有状态并控制我的记忆的方法。请参阅我对自己问题的回答。【参考方案2】:

我建议的几件事:

在打开之前尝试在条件上调用setCacheMode(CacheMode.IGNORE)

advanceScroll() 方法中,添加 if (sr != null) sr.close(); 以便在您重新分配给新的 ScrollableResults 之前关闭之前的 ScrollableResults。

一个问题:调用 setMaxSize() 的原因是什么,然后跟踪偏移量然后重新打开可滚动的结果,为什么不这样做呢?

public CriteriaReportSource(Criteria c) 
    this.c = c;
    this.sr = c.setCacheMode(CacheMode.IGNORE)
               .scroll(ScrollMode.FORWARD_ONLY);



public boolean next() 
    if (sr.next()) 
        currentObject = sr.get(0);
        return true;
    
    return false;

【讨论】:

不幸的是,此答​​案中的建议均无效。 setMaxSize() 等是通过控制查询窗口来解决问题的尝试。无论有没有它们,记忆都会继续增长。 sr.close() 的尝试也没有结果。 很抱歉听到这个消息。给 Pascal 提到的 StatelessSession,这可能是你最好的选择。【参考方案3】:

我认为我的问题之一是

if (sr.isLast()) 
    advanceScroll();
    //...

结合

((Session) Main.em.getDelegate()).clear();
//Also, "Main.em.clear()" should do...

导致一次过早地刷新数据库。这就是集合异常的原因。集合不能在 StatelessSession 中处理,因此不在讨论范围内。我不知道为什么session.evict(currentObject) 工作时Session.clear() 工作失败,但这是我现在必须处理的方式。我会把答案点扔给谁能解决这个问题。

所以,现在,我们有一个答案。需要手动滚动窗口,关闭 ScrollableResults 没有帮助,我需要正确运行 Session.clear()。

【讨论】:

这是为什么呢?这成功地解决了我的问题。虽然它确实提出了一个关于 Session.evict 的问题,我可以把它移过去,但它就是答案。

以上是关于Hibernate:遍历数百万行并且不泄漏内存的主要内容,如果未能解决你的问题,请参考以下文章

mysql中数百万行的基于键的分区

识别 Javascript 堆外的内存泄漏

插入中的Python mysql内存泄漏

数百万行的数据库设计

是否可以使用 ClickHouse 查询数百万行

具有数百万行的 Django 表