4

我需要将 1 亿多行从 MySQL 数据库加载到内存中。我的 java 程序失败,java.lang.OutOfMemoryError: Java heap space 我的机器中有 8GB RAM,并且在我的 JVM 选项中给出了 -Xmx6144m。

这是我的代码

public List<Record> loadTrainingDataSet() {

    ArrayList<Record> records = new ArrayList<Record>();
    try {
        Statement s = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY);
        s.executeQuery("SELECT movie_id,customer_id,rating FROM ratings");
        ResultSet rs = s.getResultSet();
        int count = 0;
        while (rs.next()) {

知道如何克服这个问题吗?


更新

我遇到了这篇文章,并根据下面的评论更新了我的代码。似乎我能够以相同的 -Xmx6144m 数量将数据加载到内存中,但这需要很长时间。

这是我的代码。

...
import org.apache.mahout.math.SparseMatrix;
...

@Override
public SparseMatrix loadTrainingDataSet() {
    long t1 = System.currentTimeMillis();
    SparseMatrix ratings = new SparseMatrix(NUM_ROWS,NUM_COLS);
    int REC_START = 0;
    int REC_END = 0;

    try {
        for (int i = 1; i <= 101; i++) {
            long t11 = System.currentTimeMillis();
            REC_END = 1000000 * i;
            Statement s = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
                    java.sql.ResultSet.CONCUR_READ_ONLY);
            s.setFetchSize(Integer.MIN_VALUE);
            ResultSet rs = s.executeQuery("SELECT movie_id,customer_id,rating FROM ratings LIMIT " + REC_START + "," + REC_END);//100480507
            while (rs.next()) {
                int movieId = rs.getInt("movie_id");
                int customerId = rs.getInt("customer_id");
                byte rating = (byte) rs.getInt("rating");
                ratings.set(customerId,movieId,rating);
            }
            long t22 = System.currentTimeMillis();
            System.out.println("Round " + i + " completed " + (t22 - t11) / 1000 + " seconds");
            rs.close();
            s.close();
        }

    } catch (Exception e) {
        System.err.println("Cannot connect to database server " + e);
    } finally {
        if (conn != null) {
            try {
                conn.close();
                System.out.println("Database connection terminated");
            } catch (Exception e) { /* ignore close errors */ }
        }
    }
    long t2 = System.currentTimeMillis();
    System.out.println(" Took " + (t2 - t1) / 1000 + " seconds");
    return ratings;
}

加载前 100,000 行需要 2 秒。加载第 29 个 100,000 行需要 46 秒。我在中间停止了这个过程,因为它花费了太多时间。这些时间是可以接受的吗?有没有办法提高这段代码的性能?我在 8GB RAM 64 位 Windows 机器上运行它。

4

4 回答 4

12

一亿条记录意味着每条记录最多可能占用 50 个字节以适应 6 GB + 一些额外空间用于其他分配。在 Java 中 50 个字节算不了什么;aObject[]每个元素占用 32 个字节。您必须找到一种方法来立即使用while (rs.next())循环中的结果,而不是完全保留它们。

于 2013-01-26T10:09:34.767 回答
3

问题是我在 s.executeQuery( 行它自己得到 java.lang.OutOfMemoryError

您可以将查询拆分为多个:

    s.executeQuery("SELECT movie_id,customer_id,rating FROM ratings LIMIT 0,300"); //shows the first 300 results
    //process this first result
    s.executeQuery("SELECT movie_id,customer_id,rating FROM ratings LIMIT 300,600");//shows 300 results starting from the 300th one
    //process this second result
    //etc

当找不到更多结果时,您可以做一段时间停止

于 2013-01-26T10:25:46.370 回答
2

您可以调用stmt.setFetchSize(50);conn.setAutoCommitMode(false);避免将整个 ResultSet 读入内存。

这是文档所说的:

根据光标获取结果

默认情况下,驱动程序一次收集查询的所有结果。这对于大型数据集可能很不方便,因此 JDBC 驱动程序提供了一种将 ResultSet 基于数据库游标并仅获取少量行的方法。

少量的行缓存在连接的客户端,当用尽时,通过重新定位游标来检索下一个行块。

笔记:

  • 基于游标的结果集不能在所有情况下使用。有许多限制会使驱动程序默默地退回到一次获取整个 ResultSet。
  • 与服务器的连接必须使用 V3 协议。这是服务器 7.4 及更高版本的默认设置(仅受支持)。-
  • 连接不得处于自动提交模式。后端在事务结束时关闭游标,因此在自动提交模式下,后端将在可以从中获取任何内容之前关闭游标。-
  • 必须使用 ResultSet 类型创建语句 ResultSet.TYPE_FORWARD_ONLY。这是默认设置,因此无需重写代码即可利用这一点,但这也意味着您不能向后滚动或以其他方式在 ResultSet 中跳转。-
  • 给出的查询必须是单个语句,而不是用分号串在一起的多个语句。

示例:设置 fetchsize以打开和关闭游标。

将代码更改为游标模式就像将语句的获取大小设置为适当的大小一样简单。将提取大小设置回 0 将导致所有行都被缓存(默认行为)。

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/test?useCursorFetch=true&user=root");
// make sure autocommit is off 
conn.setAutoCommit(false); 
Statement st = conn.createStatement();

// Turn use of the cursor on. 
st.setFetchSize(50);
ResultSet rs = st.executeQuery("SELECT * FROM mytable");
while (rs.next()) {
   System.out.print("a row was returned.");
} 
rs.close();

// Turn the cursor off. 
st.setFetchSize(0);
rs = st.executeQuery("SELECT * FROM mytable");
while (rs.next()) {
   System.out.print("many rows were returned.");
} 
rs.close();

// Close the statement. 
st.close();
于 2016-04-05T13:53:13.610 回答
0

您将不得不重新设计并将数据分块加载到内存中。

例子

1)使用适当的 SQL(sql 只选择 100 万条)从 DB 加载前 100 万条记录并处理 2)加载另一个类似的块。

单独的 setFetchSize 并不能解决这个问题。

于 2018-08-03T06:41:27.840 回答