我只需要使用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堆空间异常:(。
所以我猜ScrollableResults不是我想要的?处理这个问题的正确方法是什么?我不介意这个while循环需要几天时间(好吧我不喜欢它)。
我想处理这个问题的另一种方法是使用setFirstResult和setMaxResults迭代结果,只使用常规的Hibernate结果而不是ScrollableResults。这感觉就像效率低下一样,当我在第89行第89行调用setFirstResult时,会开始花费一段可笑的时间......
UPDATE:setFirstResult / setMaxResults不起作用,事实证明需要花费很长时间才能达到我担心的偏移量。这里一定有解决方案!这不是一个很标准的程序吗?我愿意放弃Hibernate并使用JDBC或其他任何东西。
更新2:我提出的解决方案可行,但不是很好,基本上是以下形式:
select * from person where id > <offset> and <other_conditions> limit 1
由于我还有其他条件,即使是索引中的所有条件,它仍然没有我想要的那么快......所以仍然可以提供其他建议..
答案 0 :(得分: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>
答案 1 :(得分:19)
你应该可以使用ScrollableResults
,虽然它需要一些神奇的咒语才能使用MySQL。我在一篇博文(http://www.numerati.com/2012/06/26/reading-large-result-sets-with-hibernate-and-mysql/)中写了我的发现,但我在这里总结一下:
&#34; [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仍会将结果缓存在Session
中,因此您需要经常调用session.evict()
或session.clear()
。如果您只是在阅读数据,可以考虑使用StatelessSession
,但您应事先阅读其文档。&#34;
答案 2 :(得分:17)
将查询中的提取大小设置为最佳值,如下所示。
此外,当不需要缓存时,最好使用StatelessSession。
ScrollableResults results = session.createQuery("SELECT person FROM Person person")
.setReadOnly(true)
.setFetchSize( 1000 ) // <<--- !!!!
.setCacheable(false).scroll(ScrollMode.FORWARD_ONLY)
答案 3 :(得分:7)
FetchSize必须为Integer.MIN_VALUE
,否则无效。
必须从官方参考资料中提取:https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-implementation-notes.html
答案 4 :(得分:3)
实际上你可能已经得到了你想要的东西 - 使用MySQL的低内存可滚动结果 - 如果你使用了这里提到的答案:
Streaming large result sets with MySQL
请注意,您将遇到Hibernate延迟加载的问题,因为它会在滚动完成之前执行的任何查询上抛出异常。
答案 5 :(得分:1)
拥有9000万条记录,听起来你应该批量选择你的SELECT。在将初始加载到分布式缓存中时,我已经完成了Oracle。查看MySQL文档,等效似乎是使用LIMIT子句:http://dev.mysql.com/doc/refman/5.0/en/select.html
以下是一个例子:
SELECT * from Person
LIMIT 200, 100
这将返回Person
表的行201到300。
您需要先从表中获取记录数,然后将其除以批量大小,然后从那里计算出循环和LIMIT
参数。
这样做的另一个好处是并行性 - 您可以在此并行执行多个线程,以加快处理速度。
处理9000万条记录听起来也不像是使用Hibernate的最佳位置。
答案 6 :(得分:1)
问题可能是,在关闭会话之前,Hibernate会保留对会话中所有对象的引用。这与查询缓存无关。在将对象写入文件之后,可能有助于从会话中逐出()对象。如果它们不再被会话引用,则垃圾收集器可以释放内存,并且不会再耗尽内存。
答案 7 :(得分:1)
我建议的不只是sample code,而是基于Hibernate
的查询模板为您执行此解决方法(pagination
,scrolling
和clearing
Hibernate会话)。
它也可以很容易地适应EntityManager
。
答案 8 :(得分:0)
我之前没有读过整个结果集就成功使用了Hibernate滚动功能。有人说MySQL没有真正的滚动游标,但它声称基于JDBC dmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE)和在它周围搜索似乎其他人已经使用过它。确保它没有缓存会话中的Person对象 - 我已经在没有实体要缓存的SQL查询中使用它。您可以在循环结束时调用evict以确保或使用sql查询进行测试。还可以使用setFetchSize来优化到服务器的次数。
答案 9 :(得分:0)
最近我解决了这样的问题,我写了一篇关于如何面对这个问题的博客。非常喜欢,我希望对任何人都有帮助。 我使用懒惰列表方法与部分征收。我将查询的限制和偏移量或分页替换为手动分页。 在我的例子中,select返回了10百万条记录,我得到它们并将它们插入“时态表”中:
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的角度来看,我通过带有惰性列表的部分请求实现了这个分页。这是一个从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>
}
另一方面,数据访问接口使用查询分页并实现一个逐步迭代的方法,每个25000条记录完成所有。
这种方法的结果可以在这里看到 http://www.arquitecturaysoftware.co/2013/10/laboratorio-1-iterar-millones-de.html
答案 10 :(得分:0)
如果你“用完RAM”的另一个选择是请求说,一列而不是整个对象How to use hibernate criteria to return only one element of an object instead the entire object?(节省了大量的CPU处理时间来启动)。
答案 11 :(得分:0)
对我来说,在设置useCursors = true时它正常工作,否则Scrollable Resultset忽略了fetch size的所有实现,在我的例子中它是5000但是Scrollable Resultset一次获取了数百万条记录,导致过多的内存使用。底层数据库是MSSQLServer。
JDBC:JTDS:SQLSERVER://本地主机:1433 / ACS; TDS = 8.0; useCursors =真