从大表中检索所有记录时如何避免OOM(Out of memory)错误?

我有一个任务是将一个巨大的表转换为自定义XML文件。 我将使用Java来完成这项工作。

如果我只是发出“SELECT * FROM customer”,它可能会返回最终导致OOM的大量数据。 我想知道,有没有办法可以在记录可用后立即处理,并在sql检索过程中从内存中删除记录?

—于2009年7月13日编辑

让我详细说明我的问题。 我有1个db服务器和1个应用服务器。 当我在应用程序中发出选择查询时,数据将从数据库服务器传输到应用服务器。

我相信(如果我错了,请纠正我)ResultSet需要等到接收到查询中的所有记录。 即使我们将获取大小设置为4,对于1000记录表,我们仍然最终在app服务器的堆内存中有1000条记录,这是正确的吗? 获取大小仅影响从/向数据库服务器的往返次数。

我的问题是,如何在它到达app服务器后立即开始处理该4个(或任何数字)记录,并将其丢弃以释放应用服务器中的内存?

我认为你可以使用与此相同的解决方案。 可滚动的结果集。

通过更多信息,我可以得到更有帮助的答案。

如果您使用的是MySQL:

stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); 

来自http://www.oracle.com/technology/tech/java/sqlj_jdbc/htdocs/jdbc_faq.html :

 java.util.Properties info = new java.util.Properties(); info.put ("user", "scott"); info.put ("password","tiger"); info.put ("defaultRowPrefetch","15"); getConnection ("jdbc:oracle:oci:@",info); 

如果您使用JDBC,则可以使用带有游标的ResultSet,您可以一次迭代一条记录。 您需要确保将XML一次写入一个文件,而不是使用DOM来构建XML。

我从我的经验中学到的一条经验法则是,您永远不会将数据库中的所有数据都带到您的应用程序服务器。 您可以做的一件事是实现一个过程来分页数据。

您可以带一页包含大约1000-5000条记录的数据,处理它们,然后再次获取下一页的数据。

导出整个表的概念。 (专家注意:我知道它的缺点。)

 import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; public class FullTableExport { public static String toXML(String s) { if (s != null) { StringBuilder b = new StringBuilder(s.length()); for (int i = 0, count = s.length(); i < count; i++) { char c = s.charAt(i); switch (c) { case '<': b.append("<"); break; case '>': b.append(">"); break; case '\'': b.append("'"); break; case '"': b.append("""); break; case '&': b.append("&"); break; default: b.append(c); } } return b.toString(); } return ""; } public static void main(String[] args) throws Exception { String table = "CUSTOMER"; int batch = 100; Class.forName("oracle.jdbc.driver.OracleDriver"); Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@server:orcl", "user", "pass"); PreparedStatement pstmt = conn.prepareStatement( "SELECT /*+FIRST_ROWS(" + batch + ") */ * FROM " + table); ResultSet rs = pstmt.executeQuery(); rs.setFetchSize(batch); ResultSetMetaData rsm = rs.getMetaData(); File output = new File("result.xml"); PrintWriter out = new PrintWriter(new BufferedWriter( new OutputStreamWriter( new FileOutputStream(output), "UTF-8")), false); out.printf("%n"); out.printf("%n", toXML(table)); int j = 1; while (rs.next()) { out.printf("\t%n", j++); for (int i = 1; i <= rsm.getColumnCount(); i++) { out.printf("\t\t%s%n", toXML(rsm.getColumnName(i)), toXML(rs.getString(i))); } out.printf("\t%n"); } out.printf("
%n", table); out.flush(); } }

编辑缺点(感谢@JS):

  • ojdbc之外没有使用外部库
  • 什么都没有关闭
  • 抛出一个通用的exception
  • 这是一种主要方法
  • 用于生成XML的print的用法
  • Oracle特定的SQL
  • 纯文本密码
  • 有些列在字符串表示中看起来很笨拙
  • UTF-8太国际化了
  • XML结构足迹很大

在哪个阶段发生OOM错误,是在数据检索或处理数据到XML文件?

如果是数据检索,则批量获取数据。 首先获取总行数,按主键对选择进行排序,并将选定的行限制为可咀嚼的大小。

如果在创建XML文件时,将每个客户的XML节点发送到System.out.println,请不要将其保存在内存中。 通过commad行启动程序并将所有输出重定向到文件;

 java MyConverter > results.txt 

在循环记录时,所有内容都保存在文件中。