4

MySQL データベースから 1 億行以上をメモリにロードする必要があります。java.lang.OutOfMemoryError: Java heap space マシンに 8GB の RAM があり、JVM オプションで -Xmx6144m を指定すると、Java プログラムが失敗します。

これは私のコードです

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

1億レコードは、6 GB +他の割り当て用の追加スペース内に収まるように、各レコードが最大50バイトを占める可能性があることを意味します。Javaでは50バイトは何もありません。単なるObject[]要素ごとに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);andを呼び出しconn.setAutoCommitMode(false);て、ResultSet 全体をメモリに読み込まないようにすることができます。

ドキュメントの内容は次のとおりです。

カーソルに基づく結果の取得

デフォルトでは、ドライバはクエリのすべての結果を一度に収集します。これは大規模なデータ セットには不便な場合があるため、JDBC ドライバーはデータベース カーソルに基づいて ResultSet を作成し、少数の行のみをフェッチする手段を提供します。

少数の行が接続のクライアント側にキャッシュされ、使い果たされると、カーソルの位置を変更して次の行ブロックが取得されます。

ノート:

  • カーソル ベースの ResultSet は、すべての状況で使用できるわけではありません。ドライバーが ResultSet 全体を一度に取得するように静かにフォールバックするようにする多くの制限があります。
  • サーバーへの接続には、V3 プロトコルを使用する必要があります。これは、サーバー バージョン 7.4 以降のデフォルトです (および、このバージョンでのみサポートされています)。
  • 接続は自動コミット モードであってはなりません。バックエンドはトランザクションの最後にカーソルを閉じるため、自動コミット モードでは、バックエンドはカーソルから何かを取得する前にカーソルを閉じます。
  • Statement は、 の ResultSet タイプで作成する必要があります ResultSet.TYPE_FORWARD_ONLY。これはデフォルトであるため、これを利用するためにコードを書き直す必要はありませんが、逆方向にスクロールしたり、ResultSet 内をジャンプしたりできないことも意味します。
  • 指定されたクエリは、セミコロンでつながれた複数のステートメントではなく、単一のステートメントである必要があります。

: fetchsizeを設定して、カーソルのオンとオフを切り替えます。

コードをカーソル モードに変更するのは、Statement のフェッチ サイズを適切なサイズに設定するのと同じくらい簡単です。フェッチ サイズを 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 に答える