58

Hibernate を使用して MySQL データベースのテーブルの各行を読み取り、それに基づいてファイルを書き込むだけです。しかし、9,000 万行あり、かなり大きいです。したがって、次のものが適切であると思われました。

ScrollableResults results = session.createQuery("SELECT person FROM Person person")
            .setReadOnly(true).setCacheable(false).scroll(ScrollMode.FORWARD_ONLY);
while (results.next())
    storeInFile(results.get()[0]);

問題は、上記が while ループに移る前に 9000 万行すべてを RAM にロードしようとすることです...そして、 OutOfMemoryError: Java heap space exceptions :(.

ScrollableResults は私が探していたものではないと思いますか? これを処理する適切な方法は何ですか?この while ループに何日もかかったとしても、私は気にしません (そうならないようにしたいのですが)。

これを処理する他の唯一の方法は、setFirstResult と setMaxResults を使用して結果を反復処理し、ScrollableResults の代わりに通常の Hibernate の結果を使用することだと思います。それは効率が悪いように感じますが、8900万行で setFirstResult を呼び出すと、途方もなく長い時間がかかり始めます...

更新: setFirstResult/setMaxResults は機能しません。私が恐れていたように、オフセットに到達するのに非常に長い時間がかかることが判明しました。ここに解決策があるはずです!これはかなり標準的な手順ではありませんか?? 私は、Hibernate をやめて、JDBC など必要なものは何でも使用したいと思っています。

更新 2: 私が思いついた解決策は、基本的には次の形式です。

select * from person where id > <offset> and <other_conditions> limit 1

私は他の条件を持っているので、すべてがインデックスにあるとしても、まだ私が望むほど速くはありません...他の提案のためにまだ開いています..

4

12 に答える 12

32

setFirstResult と setMaxResults を使用することは、私が知っている唯一のオプションです。

従来、スクロール可能な結果セットは、必要に応じて行のみをクライアントに転送していました。残念ながら、MySQL Connector/J は実際にはそれを偽造し、クエリ全体を実行してクライアントに転送するため、ドライバーは実際には結果セット全体を RAM にロードし、それをドリップ フィードします (メモリ不足の問題によって証明されます)。 . あなたは正しい考えを持っていました。それはMySQL Javaドライバーの欠点です。

これを回避する方法が見つからなかったので、通常の setFirst/max メソッドを使用して大きなチャンクをロードしました。悪いニュースをもたらして申し訳ありません。

セッション レベルのキャッシュやダーティ トラッキングなどが発生しないように、必ずステートレス セッションを使用してください。

編集:

UPDATE 2 は、MySQL J/Connector から抜け出さない限り、得られる最高のものです。ただし、クエリの制限を引き上げることができない理由はありません。インデックスを保持するのに十分な RAM がある場合、これは多少安価な操作になるはずです。少し変更して、一度にバッチを取得し、そのバッチの最高の ID を使用して次のバッチを取得します。

注: これは、other_conditionsが等価 (範囲条件は許可されていない) を使用し、インデックスの最後の列がidである場合にのみ機能します。

select * 
from person 
where id > <max_id_of_last_batch> and <other_conditions> 
order by id asc  
limit <batch_size>
于 2010-05-13T11:56:15.407 に答える
21

を使用できるはずですがScrollableResults、MySQL を操作するにはいくつかの魔法の呪文が必要です。調査結果をブログ投稿 ( http://www.numerati.com/2012/06/26/reading-large-result-sets-with-hibernate-and-mysql/ ) に書きましたが、ここで要約します。

「[JDBC] ドキュメントには次のように記載されています。

To enable this functionality, create a Statement instance in the following manner:
stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
                java.sql.ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);

これは、Hibernate API のバージョン 3.2+ で Query インターフェイスを使用して実行できます (これは Criteria でも機能するはずです)。

Query query = session.createQuery(query);
query.setReadOnly(true);
// MIN_VALUE gives hint to JDBC driver to stream results
query.setFetchSize(Integer.MIN_VALUE);
ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
// iterate over results
while (results.next()) {
    Object row = results.get();
    // process row then release reference
    // you may need to evict() as well
}
results.close();

これにより、結果セットをストリーミングできますが、Hibernate は依然として結果を にキャッシュするため、 orを頻繁Sessionに呼び出す必要があります。データを読み取るだけの場合は、 を使用することを検討できますが、事前にそのドキュメントを読む必要があります。"session.evict()session.clear()StatelessSession

于 2012-07-31T18:08:31.260 に答える
19

以下に示すように、クエリのフェッチ サイズを最適な値に設定します。

また、キャッシュが不要な場合は、StatelessSession を使用した方がよい場合もあります。

ScrollableResults results = session.createQuery("SELECT person FROM Person person")
        .setReadOnly(true)
        .setFetchSize( 1000 ) // <<--- !!!!
        .setCacheable(false).scroll(ScrollMode.FORWARD_ONLY)
于 2011-01-25T11:44:29.403 に答える
9

FetchSizeはでなければなりませんInteger.MIN_VALUE。そうでない場合は機能しません。

文字通り、公式リファレンスから取得する必要があります:https ://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-implementation-notes.html

于 2012-01-18T19:39:28.123 に答える
3

ここに記載されている回答を使用していた場合、実際には、MySQL で低メモリのスクロール可能な結果が得られた可能性があります。

MySQL を使用した大規模な結果セットのストリーミング

スクロールが完了する前に実行されたクエリで例外がスローされるため、Hibernate の遅延読み込みで問題が発生することに注意してください。

于 2010-12-10T18:47:13.787 に答える
1

問題は、セッションを閉じるまで Hibernate がセッション内のすべてのオブジェクトへの参照を保持することです。これは、クエリ キャッシングとは関係ありません。ファイルへのオブジェクトの書き込みが完了したら、セッションからオブジェクトを evict() すると役立つ場合があります。それらがセッションによって参照されなくなった場合、ガベージ コレクターはメモリを解放できるため、メモリが不足することはなくなります。

于 2010-07-15T13:19:11.737 に答える
1

9,000 万件のレコードがあるので、SELECT をバッチ処理する必要があるように思えます。分散キャッシュへの初期ロードを行うときに、Oracle を使用しました。MySQLのドキュメントを見ると、同等のものは LIMIT 句を使用しているようです: http://dev.mysql.com/doc/refman/5.0/en/select.html

次に例を示します。

SELECT * from Person
LIMIT 200, 100

これにより、テーブルの行 201 から 300 が返されPersonます。

最初にテーブルからレコード数を取得し、それをバッチ サイズで割り、そこからループとLIMITパラメータを計算する必要があります。

これのもう1つの利点は、並列処理です。これにより、複数のスレッドを並行して実行して、処理を高速化できます。

9000 万件のレコードを処理することも、Hibernate を使用するのに適しているとは思えません。

于 2010-05-13T11:45:34.643 に答える
0

最近、私はこのような問題に取り組み、その問題にどのように直面するかについてブログを書きました。とても似ています。どなたかのお役に立てば幸いです。私は部分買収でレイジーリストアプローチを使用しています。i 制限とオフセット、またはクエリのページ付けを手動のページ付けに置き換えました。私の例では、select は 1,000 万件のレコードを返します。それらを取得して「テンポラル テーブル」に挿入します。

create or replace function load_records ()
returns VOID as $$
BEGIN
drop sequence if exists temp_seq;
create temp sequence temp_seq;
insert into tmp_table
SELECT linea.*
FROM
(
select nextval('temp_seq') as ROWNUM,* from table1 t1
 join table2 t2 on (t2.fieldpk = t1.fieldpk)
 join table3 t3 on (t3.fieldpk = t2.fieldpk)
) linea;
END;
$$ language plpgsql;

その後、各行をカウントせずに、割り当てられたシーケンスを使用してページネーションを行うことができます:

select * from tmp_table where counterrow >= 9000000 and counterrow <= 9025000

Java の観点から、遅延リストを使用した部分的な買収を通じて、このページネーションを実装しました。これは、Abstract リストを拡張し、get() メソッドを実装したリストです。get メソッドは、データ アクセス インターフェイスを使用して、次のデータ セットの取得を続行し、メモリ ヒープを解放できます。

@Override
public E get(int index) {
  if (bufferParcial.size() <= (index - lastIndexRoulette))
  {
    lastIndexRoulette = index;
    bufferParcial.removeAll(bufferParcial);
    bufferParcial = new ArrayList<E>();
        bufferParcial.addAll(daoInterface.getBufferParcial());
    if (bufferParcial.isEmpty())
    {
        return null;
    }

  }
  return bufferParcial.get(index - lastIndexRoulette);<br>
}

一方、データ アクセス インターフェイスはクエリを使用してページ分割し、1 つのメソッドを実装して段階的に反復し、25000 レコードごとにすべてを完了します。

このアプローチの結果は、ここで見ることができます http://www.arquitecturaysoftware.co/2013/10/laboratorio-1-iterar-millones-de.html

于 2013-10-28T16:18:27.287 に答える
0

「RAMが不足している」場合の別のオプションは、オブジェクト全体ではなく1列だけを要求することです オブジェクト全体ではなく、オブジェクトの1つの要素のみを返すために休止状態基準を使用する方法は? (起動するための CPU プロセス時間を大幅に節約できます)。

于 2015-04-16T21:15:00.140 に答える
0

結果セット全体を読み込まずに、以前は Hibernate スクロール機能を正常に使用していました。MySQL は真のスクロール カーソルを実行しないと誰かが言いましたが、JDBC dmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE) に基づいており、その周りを検索すると主張しています。他の方も利用されているようです。セッションで Person オブジェクトをキャッシュしていないことを確認してください。キャッシュするエンティティがない SQL クエリで使用しました。ループの最後で evict を呼び出して確認するか、SQL クエリでテストすることができます。また、setFetchSize をいじって、サーバーへのトリップ数を最適化します。

于 2010-05-13T18:25:53.173 に答える