web-dev-qa-db-ja.com

HibernateのScrollableResultsを使用して、9000万件のレコードをゆっくり読み取る

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

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ヒープ領域の例外:(。

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

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

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

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

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

私は他の条件を持っているので、すべてがインデックスに含まれていても、それは私が望んでいるほど速くはありません...

50
at.

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

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

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

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

編集:

MySQL J/Connectorから抜け出さない限り、UPDATE 2は最高です。クエリの制限を超えられない理由はありませんが。インデックスを保持するのに十分な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>
29
Michael

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は引き続きSessionに結果をキャッシュするため、session.evict()またはsession.clear()を頻繁に呼び出す必要があります。 。データの読み取りのみを行う場合は、StatelessSessionの使用を検討してください。ただし、事前にそのドキュメントを読む必要があります。

19
Sean S.

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

また、キャッシュが不要な場合は、StatelessSessionを使用することをお勧めします。

ScrollableResults results = session.createQuery("SELECT person FROM Person person")
        .setReadOnly(true)
        .setFetchSize( 1000 ) // <<--- !!!!
        .setCacheable(false).scroll(ScrollMode.FORWARD_ONLY)
17
Haris

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

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

7
ChechuHa

実際に、ここで述べた答えを使用していた場合は、MySQLでメモリ不足のスクロール可能な結果を​​得ることができました。

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

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

3
einnocent

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

1
Reboot

サンプルコード 以上を提案しますが、Hibernateに基づくクエリテンプレートを使用して、この回避策を実行できます(paginationscrollingclearing Hibernateセッション)。

EntityManagerを使用するように簡単に適合させることもできます。

1
smalbequi

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

次に例を示します。

SELECT * from Person
LIMIT 200, 100

これは、Personテーブルの行201〜300を返します。

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

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

また、9000万件のレコードを処理することは、Hibernateを使用するためのスイートスポットのようには聞こえません。

1
SteveD

私にとっては、useCursors = trueを設定すると正常に機能しました。そうでない場合、スクロール可能な結果セットはフェッチサイズのすべての実装を無視します。基礎となるDBはMSSQLServerです。

jdbc:jtds:sqlserver:// localhost:1433/ACS; TDS = 8.0; useCursors = true

0
manu

「RAMを使い果たしている」場合のもう1つのオプションは、オブジェクト全体ではなく1つの列だけを要求することです 。 object? (ブートするために多くのCPUプロセス時間を節約します)。

0
rogerdpack

最近、私はこのような問題に取り組み、その問題にどのように直面しているかについてブログを書きました。とても似ています私は部分的買収でレイジーリストアプローチを使用します。 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パースペクティブ、私は怠zyなリストでの部分的な広告を通じてこのページネーションを実装しました。これは、抽象リストから拡張され、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>
}

一方、データアクセスインターフェイスはクエリを使用してページ分割を行い、15,000のメソッドを実装して、25,000レコードごとにすべてを完了します。

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

0
user2928872

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

0
Brian Deterling