使用Hibernate的ScrollableResults缓慢读取9000万条logging
我只需要使用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]);
问题是,上述将试图加载所有9000万行到RAM之前转移到while循环…这将杀死我的内存与OutOfMemoryError:Java堆空间exception:(。
所以我想ScrollableResults不是我正在寻找? 处理这个问题的正确方法是什么? 我不介意,如果这个while循环需要几天(好吧我不喜欢它)。
我想唯一的方法来处理这个是使用setFirstResult和setMaxResults遍历结果,只是使用常规的Hibernate结果,而不是ScrollableResults。 这感觉就像是效率不高,而且在第89万行调用setFirstResult的时候会开始花费很长的时间。
更新:setFirstResult / setMaxResults不起作用,事实certificate需要很长时间才能到达像我担心的偏移量。 这里必须有一个解决scheme! 这不是一个相当标准的程序? 我愿意放弃Hibernate并使用JDBC或其他方法。
更新2:我提出的解决scheme,工作好,不是很好,基本上是这样的forms:
select * from person where id > <offset> and <other_conditions> limit 1
由于我还有其他的条件,即使是在索引中的所有条件,它仍然没有我想要的那么快……所以还是有其他的build议。
使用setFirstResult和setMaxResults是我所知道的唯一选项。
传统上,可滚动的结果集只会根据需要将行传输到客户端。 不幸的是,MySQL Connector / J实际上伪装了它,它执行整个查询并将其传输到客户端,所以驱动程序实际上已经将整个结果集加载到RAM中,并将滴灌给您(由您的内存不足问题certificate) 。 你有正确的想法,这只是在MySQL的Java驱动程序的缺点。
我发现没有办法解决这个问题,所以用普通的setFirst / max方法加载大块。 对不起,成为坏消息的使者。
只要确保使用无状态会话,所以没有会话级caching或脏跟踪等。
编辑:
你的更新2是最好的,除非你打破了MySQL J /连接器。 虽然没有理由不能达到查询的限制。 假如你有足够的内存来保存索引,这应该是一个有点便宜的操作。 我会稍微修改它,并且一次抓取一个批次,并使用该批次的最高ID来抓取下一批次。
注意:如果other_conditions使用相等(不允许范围条件)并且索引的最后一列为id ,则这将仅适用。
select * from person where id > <max_id_of_last_batch> and <other_conditions> order by id asc limit <batch_size>
你应该可以使用ScrollableResults
,尽pipe它需要一些魔法咒语来处理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);
这可以在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();
这允许你在结果集上进行stream式处理,但是Hibernate仍然会在Session
caching结果,所以你需要经常调用session.evict()
或session.clear()
。 如果你只是在读数据,你可能会考虑使用一个StatelessSession
,不过你应该事先阅读它的文档。
将查询中的获取大小设置为如下所示的最佳值。
另外,当不需要caching时,最好使用StatelessSession。
ScrollableResults results = session.createQuery(“SELECT person FROM Person person”).setReadOnly(true)。 setFetchSize(1000) .setCacheable(false).scroll(ScrollMode.FORWARD_ONLY)
FetchSize必须是Integer.MIN_VALUE
,否则将无法工作。
它必须从字面上从官方的参考: http : //dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html
其实你可能已经得到了你想要的东西 – 与MySQL的低内存可滚动结果 – 如果你已经使用了这里提到的答案:
使用MySQLstream式传输大型结果集
请注意,Hibernate的延迟加载会遇到问题,因为在滚动完成之前执行的任何查询都会引发exception。
有9000万条logging,这听起来像你应该批量你的select。 在初始加载到分布式caching中时,我已经完成了Oracle的工作。 看看MySQL文档,相当于使用LIMIT子句: http : //dev.mysql.com/doc/refman/5.0/en/select.html
这是一个例子:
SELECT * from Person LIMIT 200, 100
这将返回Person
表的行201到300。
您需要先从表格中获取logging计数,然后将其除以批量大小,然后从中计算出循环和LIMIT
参数。
这样做的另一个好处是并行性 – 您可以并行执行多个线程来实现更快的处理。
处理9000万条logging听起来也不像使用Hibernate的最佳位置。
问题可能是,Hibernate保持对会话中所有对象的引用,直到closures会话。 这与查询caching无关。 也许这将有助于在完成将对象写入文件之后从对话中逐出对象。 如果它们不再被会话引用,则垃圾收集器可以释放内存,并且不会再耗尽内存。
我提出的不仅仅是一个示例代码 ,而是一个基于Hibernate
的查询模板来为你做这个解决方法( pagination
, scrolling
和clearing
Hibernate会话)。
它也可以很容易地适应使用一个EntityManager
。
我之前成功地使用了Hibernate滚动function,而没有读取整个结果集。有人说MySQL不会做真正的滚动游标,但它声称基于JDBC dmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE)并在其周围search看起来像其他人已经使用它。 确保它不在会话中cachingPerson对象 – 我在没有实体caching的SQL查询中使用它。 您可以在循环结束时调用evict来确定或者用sql查询来testing。 另外还可以使用setFetchSize来优化到服务器的访问次数。
最近我在这个问题上工作,我写了一个关于如何面对这个问题的博客。 非常喜欢,我希望对任何人都有帮助。 我使用懒列表方法进行部分search。 我将查询的限制和偏移或分页replace为手动分页。 在我的例子中,select返回了10百万logging,我得到它们并将它们插入到“时态表”中:
create or replace function load_records () returns VOID as $$ BEGIN drop sequence if exists temp_seq; create temp sequence temp_seq; insert into tmp_table SELECT linea.* FROM ( select nextval('temp_seq') as ROWNUM,* from table1 t1 join table2 t2 on (t2.fieldpk = t1.fieldpk) join table3 t3 on (t3.fieldpk = t2.fieldpk) ) linea; END; $$ language plpgsql;
在那之后,我可以分页但不使用每行,但使用分配的序列:
select * from tmp_table where counterrow >= 9000000 and counterrow <= 9025000
从java的angular度来看,我实现了这个分页通过部分采购与懒惰列表。 这是一个从Abstract列表扩展并实现get()方法的列表。 get方法可以使用数据访问接口继续获取下一组数据并释放内存堆:
@Override public E get(int index) { if (bufferParcial.size() <= (index - lastIndexRoulette)) { lastIndexRoulette = index; bufferParcial.removeAll(bufferParcial); bufferParcial = new ArrayList<E>(); bufferParcial.addAll(daoInterface.getBufferParcial()); if (bufferParcial.isEmpty()) { return null; } } return bufferParcial.get(index - lastIndexRoulette);<br> }
另一方面,数据访问接口使用查询分页和实现一个方法逐步迭代,每个logging完成25000条logging。
这种方法的结果可以在这里看到http://www.arquitecturaysoftware.co/2013/10/laboratorio-1-iterar-millones-de.html
另一个select,如果你“内存不足”是只要求说,一列而不是整个对象如何使用hibernate条件只返回一个对象的元素,而不是整个对象? (节省大量的CPU进程时间来启动)。