mysql使用ResultSet时内存(RAM)使用量增加?

5

我正在使用MySQL和Java来选择约50000条记录。奇怪的是,当我使用ResultSet和next()方法读取数据时,我注意到在获取期间我的java应用程序的RAM使用量增加了。它从255 MB开始增加到379 MB!我正在使用的代码如下:

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、Statement和Connection,并转到程序的其他部分之后,程序的内存使用量仍然不会减少!谢谢


2
MySQL会缓存查询结果。 - Johan
好的,谢谢。但是我该如何清空缓存? - Soheil
你可以为了节省内存(尽管会花费更多时间),限制每个SELECT语句返回的结果数量。 - RMT
4个回答

6

使用 Statement.setFetchSize() 提供提示给驱动程序,表明应该为包含特定数量行的结果集流式传输。据我所知,MySQL Connector-J 驱动程序能够理解这个提示并流式传输 ResultSet(但在 MySQL 的情况下仅限于一次发送一行)。

默认值为 0,将确保 Connector-J 驱动程序获取完整的 ResultSet 而不进行流式传输。因此,在 MySQL 的情况下,您需要提供一个显式值-Integer.MIN_VALUE。

声明:

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

该语句不会导致ResultSet流式传输(至少不是自动的)。它仅确保结果集不可“滚动”(即只能向前遍历)和不可更新,且在事务提交时底层游标将被关闭。

正如在MySQL的JDBC实现注意事项中所指出的,上述语句(没有ResultSet.CLOSE_CURSORS_AT_COMMIT参数)必须与Statement.setFetchSize(Integer.MIN_VALUE)一起使用才能逐行进行流式传输。相关的注意事项也已经记录下来了。

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


不,MySQL JDBC驱动程序仅对控制获取大小提供非常有限的支持。默认行为是一次性获取选择的整个结果。要流式传输结果,必须将获取大小设置为Integer.MIN_VALUE,并使用ResultSet.TYPE_FORWARD_ONLY、ResultSet.CONCUR_READ_ONLY创建语句。这样做,还必须考虑到文档中指出的几个进一步的限制:http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html - jarnbjo
我已经使用了这些代码: st.setFetchSize(500); rsDBReader.setFetchSize(500); 但是它没有改变任何东西。 - Soheil
@Soheil,文档非常清楚地说明500不会触发提示。我建议先阅读文档。如果你想要一个快速的答案,使用Integer.MIN_VALUE代替500 可能 有效。 - Vineet Reynolds
@Vineet Reynolds,你是正确的。我已经使用Integer.MIN_VALUE测试过了。它有效。 - Soheil

2
我建议在查询中限制检索的行数。50000是很多的,为什么不使用一个循环每次获取1000行呢?
您可以使用limit语句来实现,如这里所述。最好对您正在处理的数据量保持务实态度。您当前的选择可能今天返回50000行,但如果明天增长到一百万呢?您的应用程序将会崩溃。因此,请逐步执行处理步骤。

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

Postgres JDBC文档


-1
你看到的其实是期望的行为,不一定表示存在内存泄漏。在Java中,对象实例在变得不可达后不会立即被垃圾收集,并且大多数Java虚拟机都不愿意将已分配的内存返还给操作系统。
如果你正在使用最新版本的Oracle Java虚拟机并且需要更积极的垃圾回收器,可以尝试通过添加以下参数到java命令来使用G1GC实现:
-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC
G1GC垃圾回收器通常比默认的垃圾回收器更快地回收对象,并且未使用的内存也将被释放。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接