使用Hibernate的ScrollableResults慢慢读取9000万条记录

at.*_*at. 52 java mysql hibernate large-data-volumes scrollableresults

我只需要使用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]);
Run Code Online (Sandbox Code Playgroud)

问题是上面将尝试将所有9000万行加载到RAM中,然后再转到while循环...这将使OutOfMemoryError消除我的内存:Java堆空间异常:(.

所以我猜ScrollableResults不是我想要的?处理这个问题的正确方法是什么?我不介意这个while循环需要几天(好吧我不喜欢它).

我想处理这个问题的另一种方法是使用setFirstResult和setMaxResults迭代结果,只使用常规的Hibernate结果而不是ScrollableResults.这感觉就像它效率低下一样,当我在8900万行中调用setFirstResult时,它将开始花费一段可笑的时间......

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

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

select * from person where id > <offset> and <other_conditions> limit 1
Run Code Online (Sandbox Code Playgroud)

由于我有其他条件,即使是索引中的所有条件,它仍然没有我想要的那么快......所以仍然可以提供其他建议..

Mic*_*ael 29

使用setFirstResult和setMaxResults是我唯一知道的选项.

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

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

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

编辑:

除非你突破MySQL J/Connector,否则你的UPDATE 2是最好的.虽然没有理由你不能提高查询的限制.如果你有足够的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>
Run Code Online (Sandbox Code Playgroud)

  • @Michael你是如何发现MySQL连接器伪造滚动的?它写在某处吗?我感兴趣因为我喜欢使用NHibernate的滚动功能,我正在使用.NET的mysql连接器,我想检查Mysql .Net连接器是否也伪造它,或者取决于版本? (3认同)
  • 使用 StatelessSession 是一个特别好的技巧! (2认同)

小智 20

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

"[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);
Run Code Online (Sandbox Code Playgroud)

这可以在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();
Run Code Online (Sandbox Code Playgroud)

这允许您对结果集进行流式传输,但是Hibernate仍会将结果缓存在中Session,因此您需要经常调用session.evict()或者session.clear()每隔一段时间调用一次.如果你只是在阅读数据,你可以考虑使用a StatelessSession,尽管你应该事先阅读它的文档."

  • 为什么Session#flush()具有只读会话?你确定你不是指Session#evict(row)或Session#clear(),这有助于控制第一级缓存的大小. (3认同)

Har*_*ris 17

将查询中的提取大小设置为最佳值,如下所示.

此外,当不需要缓存时,最好使用StatelessSession.

ScrollableResults results = session.createQuery("SELECT person FROM Person person")
        .setReadOnly(true)
        .setFetchSize( 1000 ) // <<--- !!!!
        .setCacheable(false).scroll(ScrollMode.FORWARD_ONLY)
Run Code Online (Sandbox Code Playgroud)


小智 7

必须是FetchSize Integer.MIN_VALUE,否则它将无效.

它必须从官方参考文字中获取:https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-implementation-notes.html