2011-08-04 129 views
4

我正在使用MySQL和Java来选择约50000条记录。 奇怪的是,当我使用ResultSet和next()方法来读取数据,我看到,在取我的Java应用程序增加RAM的使用。它以255 MB开始并增加到379 MB! 我使用的代码是在这里:mysql使用ResultSet时内存(RAM)使用率增加了吗?

try { 
    Class.forName("com.mysql.jdbc.Driver"); 
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/#mysql50#crawler - used in report?" + "user=root&password=&useUnicode=true&characterEncoding=UTF-8"); 
    Statement st = conn.createStatement(); 
    ResultSet rsDBReader = st.executeQuery("SELECT Id, Content FROM DocsArchive"); 
    while (rsDBReader.next()) { 
     int docId = rsDBReader.getInt(1); 
     String content = rsDBReader.getString(2); 
     . . . 
     } 
    rsDBReader.close(); 
    st.close(); 
    conn.close(); 
} catch (Exception e) { 
    System.out.println("Exception in reading data: " + e); 
} 

我相信,内存使用量是ResultSet中,而不是程序的其它部分。 在这个程序中,我不需要更新记录,所以我想在完成工作后删除每条记录。 我的猜测是,已经读过的记录不会被删除,程序也不会释放他们的记忆。所以我用了一些技巧来避免这种情况,比如使用下面的代码:

Statement st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT); 

st.setFetchSize(500); 
rsDBReader.setFetchSize(500); 

但是他们没有改变任何东西。 :(

所以我需要一些方法,消除行(发行版)的内存已读取。

另一个有趣的一点是,即使在完成功能和关闭的ResultSet,语句和连接,并且要经过该计划的另一部分,仍然是程序存储器的使用不会降低! 谢谢

+2

MySQL高速缓存查询结果。 – Johan

+0

好的谢谢,但是我怎样才能清空咖啡? – Soheil

+0

你可以做什么,以节省内存(更多的时间虽然)是限制每个SELECT语句 – RMT

回答

2

我建议你限制在查询中检索行的数量。50000是很多,为什么不能有一个循环其获取,让我们说,1000行每一次?

你可以做到这一点使用limit语句,描述here。对于您正在处理的数据量来说,最好务实。你目前的选择今天可能会返回50000行,但如果明天增长到100万呢?你的应用程序会窒息。所以,一步一步做你的处理。

6

使用Statement.setFetchSize()向驱动程序提供一个提示,告知驱动程序它应该为包含一定行数的数据流传输ResultSet。据我所知,MySQL连接-J驱动程序不明白的提示和溪流ResultSet S(但这是在MySQL中的情况下,时间限制为行)。

默认值为0,将确保连接器-J驱动程序将获取完整ResultSet没有进行流式传输。这就是为什么你需要提供一个明确的值--MySQL的Integer.MIN_VALUE。

声明:

Statement st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT); 

不会导致流的ResultSet(至少不是在它自己的协议)。它仅仅确保了结果集不为“滚动”(即只能在向前的方向上遍历),而不是“可更新的”,并且当事务提交底层光标将被关闭。

JDBC implementation notes of MySQL所述,必须调用以上语句(不包括ResultSet.CLOSE_CURSORS_AT_COMMIT参数)并结合Statement.setFetchSize(Integer.MIN_VALUE)调用来逐个发生流式传输。有关这种情况的相关警告也被记录在案。

请注意,游标的可保存性未在MySQL文档中提到的示例中指定。如果您需要的值不同于Connection.getHoldability()提供的值,那么此建议可能不适用。

+0

不,MySQL JDBC驱动程序只用于控制提取大小非常有限的支持得到的结果数。默认行为是一次获取select的整个结果。要传输结果,必须将读取大小设置为Integer.MIN_VALUE,并使用ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY创建Statement。这样做,有几个其他限制必须考虑,如文档中指出:http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html – jarnbjo

+0

我已经使用了这些代码: st.setFetchSize(500); rsDBReader.setFetchSize(500); 但它没有改变任何东西 – Soheil

+0

@ jarnbjo,谢谢。原来我只是部分正确。 –

-1

你看到的其实是正常现象,不必一定表示内存泄漏。 Java中的对象实例在它们变得无法访问后不会立即收集垃圾,大多数Java虚拟机很不愿意将一次分配的内存返回给操作系统。

如果您使用的是最新的Oracle的Java虚拟机的版本,确实需要更积极的垃圾收集器,您可以通过添加以下参数到java命令尝试G1GC实现:

-XX:+ UnlockExperimentalVMOptions - XX:+ UseG1GC

的G1GC垃圾收集器通常回收对象的速度比默认的垃圾收集和未使用的存储器也由过程释放。

0

注意有类似的问题与的Postgres的最新版本。为了实现*你需要禁用自动提交对连接connection.setAutoCommit(false),并在您的SQL语句(只包含一个分号即语句)使用单个语句游标处理。它为我工作。

Postgres JDBC documentation