使用Hibernate的RollableResults可以缓慢读取9000万条记录

我只需要使用Hibernate读取MySQL数据库中表中的每一行,并基于它编写一个文件。但是有9000万行,它们非常大。因此,以下似乎是合适的:

ScrollableResults results = session.createQuery("SELECT person FROM Person person")
            .setReadOnly(true).setCacheable(false).scroll(ScrollMode.FORWARD_ONLY);
while (results.next())
    storeInFile(results.get()[0]);

问题是,在进入while循环之前,上述将尝试将所有9000万行加载到RAM中...这将扼杀我的记忆与OutOfMemoryError:Java堆空间异常:(。

所以我想可滚动的结果不是我想要的吗?处理这个问题的正确方法是什么?我不介意这个同时循环是否需要几天(好吧,我希望不要这样做)。

我想处理这个问题的唯一其他方法是使用setFirstResult和setMaxResults来迭代结果,并且只使用常规的Hibernate结果而不是ScrollableResults。这感觉效率低下,当我在第8900万行调用setFirstResult时,它将开始花费可笑的很长时间......

更新:setFirstResult/setMaxResults不起作用,事实证明,像我担心的那样,需要很长时间才能达到偏移量。这里一定有解决方案!这不是一个非常标准的程序吗?我愿意放弃Hibernate,使用JDBC或任何需要的东西。

更新2:我想出的解决方案工作正常,不是很好,基本上是以下形式:

select * from person where id > <offset> and <other_conditions> limit 1

由于我有其他条件,即使全部在索引中,它仍然没有我希望的那么快......所以仍然开放其他建议..


答案 1

使用 setFirstResult 和 setMaxResults 是我所知道的唯一选择。

传统上,可滚动的结果集只会根据需要将行传输到客户端。不幸的是,MySQL Connector / J实际上是伪造的,它执行整个查询并将其传输到客户端,因此驱动程序实际上将整个结果集加载到RAM中,并将其滴灌到您那里(您的内存不足问题证明了这一点)。你有正确的想法,这只是MySQL java驱动程序的缺点。

我发现没有办法解决这个问题,所以使用常规的setFirst / max方法加载大块。很抱歉成为坏消息的带来者。

只需确保使用无状态会话,这样就没有会话级缓存或脏跟踪等。

编辑:

你的 UPDATE 2 是你最好的,除非你突破了 MySQL J/Connector。虽然没有理由不能提高查询的限制。如果您有足够的RAM来保存索引,这应该是一个便宜的操作。我会稍微修改它,一次抓取一个批次,然后使用该批次的最高 id 来抓取下一批。

注意:这只有在other_conditions使用相等(不允许范围条件)并将索引的最后一列作为id时才有效。

select * 
from person 
where id > <max_id_of_last_batch> and <other_conditions> 
order by id asc  
limit <batch_size>

答案 2

你应该能够使用 ,尽管它需要一些神奇的咒语才能使用MySQL。我在一篇博客文章(http://www.numerati.com/2012/06/26/reading-large-result-sets-with-hibernate-and-mysql/)中写下了我的发现,但我会在这里总结:ScrollableResults

“[JDBC] 文档说:

To enable this functionality, create a Statement instance in the following manner:
stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
                java.sql.ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);

这可以使用 Hibernate API 版本 3.2+ 中的 Query 接口(这应该也适用于 Criteria)来完成:

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();

这允许您对结果集进行流式传输,但是Hibernate仍将在中缓存结果,因此您需要调用或每隔一段时间。如果您只是在读取数据,则可以考虑使用 ,但您应该事先阅读其文档。Sessionsession.evict()session.clear()StatelessSession


推荐