3

組み込みの firebird データベース エンジンと Java SE に大きな問題があります。私は現在、ユーザーがデータを除外するためのフィルタリング ツールを開発しています。そのため、フィルタリング用に 2 つのオプションを作成しました。ユーザーは 1 つまたは両方を選択できます。

  1. ブラック リストから除外します (ブラック リストはユーザーによって制御されます)。
  2. これまでにアップロードされて除外されたすべてのレコードを記録する膨大なリストに従って除外します。

ユーザーがアップロードするデータは、プレーン テキストのカンマまたはトークンで次のように区切られます。

(SET OF COLUMNS)| RECORD TO FILTER |
0-MANY COLUMNS  |       ABC2       |
0-MANY COLUMNS  |       ABC5       |

DBにアップロードするとき、すべてのフィルターにA FLAGを追加します

(SET OF COLUMNS) | RECORD TO FILTER | FLAG FOR FIlTER A | FLAG FOR FILTER B  |
0-MANY COLUMNS   |       ABC2       |                   |                    |
0-MANY COLUMNS   |       ABC5       |                   |                    | 

したがって、2 番目のフィルターに関しては、プログラムはソフトウェアの最初の実行時にメインの空のテーブルを持ち、最初のアップロードからのすべてのレコードでそのテーブルを埋めます。ユーザーがテキストを数回アップロードすると、メイン テーブルには次のテーブルのような一意のレコードが作成されます。

 Record |      Date criteria for filtering      |
 ABC1   | 08/11/2012:1,07/11/2012:3,06/11/2012:5|
 ABC2   | 05/11/2012:1,04/11/2012:0,03/11/2012:0|
 ABC3   | 12/11/2012:3,11/11/2012:0,10/11/2012:0|
 ABC4   | 12/11/2012:1,11/11/2012:0,10/11/2012:0|
 ABC5   | 12/11/2012:3,11/11/2012:0,10/11/2012:3|
 ABC9   | 11/11/2012:3,10/11/2012:1,09/11/2012:0|

たとえば、データが処理されると、ソフトウェアはメイン テーブルとユーザー テーブルの両方を更新します。

(SET OF COLUMNS| RECORD TO FILTER | FLAG FOR FIlTER A | FLAG FOR FILTER B  |
0-MANY COLUMNS |       ABC4       |                   |                    | 
0-MANY COLUMNS |       ABC9       |                   |                    | 

したがって、メイン テーブルが更新されます。

 Record |      Day criteria for filtering      |
 ABC1   | 08/11/2012:1,07/11/2012:3,06/11/2012:5|
 ABC2   | 05/11/2012:1,04/11/2012:0,03/11/2012:0|
 ABC3   | 12/11/2012:3,11/11/2012:0,10/11/2012:0|
 ABC4   | 12/11/2012:1,11/11/2012:0,10/11/2012:0| ->12/11/2012:2,11/11/2012:0,10/11/2012:0
 ABC5   | 12/11/2012:3,11/11/2012:0,10/11/2012:3|
 ABC9   | 11/11/2012:3,10/11/2012:1,09/11/2012:0| ->12/11/2012:1,11/11/2012:3,10/11/2012:1

過去 3 日間でデータ基準イベントが 4 つ以上に達した場合、ユーザー テーブルはフィルター B にフラグを立てます。各日付の横に整数があることに注意してください。

(SET OF COLUMNS)| RECORD TO FILTER | FLAG FOR FIlTER A | FLAG FOR FILTER B  |
 0-MANY COLUMNS |       ABC4       |                   |                    | 
 0-MANY COLUMNS |       ABC9       |                   |          X         | 

どちらの更新も 1 つのトランザクションで行われます。問題は、ユーザーが 800,000 を超えるレコードをアップロードすると、プログラムが while ループで次の例外をスローすることです。変更可能な日の文字列で最大のパフォーマンスを得るために、StringBuilder の解析メソッドと追加メソッドを使用します。

java.lang.OutOfMemoryError: Java ヒープ領域

これが私のコードです。5日間使用します。

FactoriaDeDatos factoryInstace = FactoriaDeDatos.getInstance();
Connection cnx = factoryInstace.getConnection();
cnx.setAutoCommit(false);
PreparedStatement pstmt = null;
ResultSet rs=null;
pstmt = cnx.prepareStatement("SELECT CM.MAIL,CM.FECHAS FROM TCOMERCIALMAIL CM INNER JOIN TEMPMAIL TMP ON CM.MAIL=TMP."+colEmail);
rs=pstmt.executeQuery();
pstmtDet = cnx.prepareStatement("ALTER INDEX IDX_MAIL INACTIVE");
pstmtDet.executeUpdate();
pstmtDet = cnx.prepareStatement("SET STATISTICS INDEX IDX_FECHAS");
pstmtDet.executeUpdate();
pstmtDet = cnx.prepareStatement("ALTER INDEX IDX_FECHAS INACTIVE");
pstmtDet.executeUpdate();
pstmtDet = cnx.prepareStatement("SET STATISTICS INDEX IDX_FECHAS");
pstmtDet.executeUpdate();
sql_com_local_tranx=0;
int trxNum=0;
int ix=0;
int ixE1=0;
int ixAc=0;
StringBuilder sb;
StringTokenizer st;
String fechas;
int pos1,pos2,pos3,pos4,pos5,pos6,pos7,pos8,pos9;
StringBuilder s1,s2,sSQL,s4,s5,s6,s7,s8,s9,s10;
long startLoop = System.nanoTime();

long time2 ;
boolean ejecutoMax=false;
//int paginador_sql=1000;
//int trx_ejecutada=0;
sb=new StringBuilder();
s1=new StringBuilder();
s2=new StringBuilder();
sSQL=new StringBuilder();
s4=new StringBuilder();
s6=new StringBuilder();
s8=new StringBuilder();
s10=new StringBuilder();
while(rs.next()){
   //De aqui
   actConteoDia=0;
   sb.setLength(0);
   sb.append(rs.getString(2));
   pos1= sb.indexOf(":",0);  
   pos2= sb.indexOf(",",pos1+1);  
   pos3= sb.indexOf(":",pos2+1);  
   pos4= sb.indexOf(",",pos3+1);  
   pos5= sb.indexOf(":",pos4+1);
   pos6= sb.indexOf(",",pos5+1);
   pos7= sb.indexOf(":",pos6+1);
   pos8= sb.indexOf(",",pos7+1);
   pos9= sb.indexOf(":",pos8+1);
   s1.setLength(0);
   s1.append(sb.substring(0, pos1));
   s2.setLength(0);
   s2.append(sb.substring(pos1+1, pos2));
   s4.setLength(0);
   s4.append(sb.substring(pos3+1, pos4));
   s6.setLength(0);
   s6.append(sb.substring(pos5+1, pos6));
   s8.setLength(0);
   s8.append(sb.substring(pos7+1, pos8));
   s10.setLength(0);
   s10.append(sb.substring(pos9+1));
   actConteoDia=Integer.parseInt(s2.toString());
   actConteoDia++;
   sb.setLength(0);
   //sb.append(s1).a
   if(actConteoDia>MAXIMO_LIMITE_POR_SEMANA){
      actConteoDia=MAXIMO_LIMITE_POR_SEMANA+1;
   }
   sb.append(s1).append(":").append(actConteoDia).append(",").append(rs.getString(2).substring(pos2+1, rs.getString(2).length()));
   //For every date record it takes aprox 8.3 milisec by record

   sSQL.setLength(0);
   sSQL.append("UPDATE TCOMERCIALMAIL SET FECHAS='").append(sb.toString()).append("' WHERE MAIL='").append(rs.getString(1)).append("'");

   pstmtDet1.addBatch(sSQL.toString());
   //actConteoDia=0;
   //actConteoDia+=Integer.parseInt(s2.toString());
   actConteoDia+=Integer.parseInt(s4.toString());
   actConteoDia+=Integer.parseInt(s6.toString());
   actConteoDia+=Integer.parseInt(s8.toString());
   actConteoDia+=Integer.parseInt(s10.toString());
   if(actConteoDia>MAXIMO_LIMITE_POR_SEMANA){
      sSQL.setLength(0);
      sSQL.append("UPDATE TEMPMAIL SET DIASLIMITE='S' WHERE ").append(colEmail).append("='").append(rs.getString(1)).append("'");
      pstmtDet.addBatch(sSQL.toString());
   }

   sql_com_local_tranx++;

   if(sql_com_local_tranx%2000==0 || sql_com_local_tranx%7000==0  ){
      brDias.setString("PROCESANDO "+sql_com_local_tranx);
      pstmtDet1.executeBatch();
      pstmtDet.executeBatch();

   }
   if(sql_com_local_tranx%100000==0){
       System.gc();
       System.runFinalization();
   }
}

pstmtDet1.executeBatch();
pstmtDet.executeBatch();
cnx.commit();

問題がどこにあるかを追跡できるように、テレメトリ テストを行いました。それが問題である間は大きいと思いますが、問題が正確にどこにあるのかわかりません。テレムトリー テストの画像をいくつか追加しています。正しく解釈する必要があります。

gc は、jvm がオブジェクトを存続させる時間とは逆になります。

http://imageshack.us/photo/my-images/849/66780403.png

メモリ ヒープが 50 MB から 250 MB になり、使用されたヒープが 250 MB に達すると、outOfMemory 例外が発生します。

50 MB
http://imageshack.us/photo/my-images/94/52169259.png

250 MB に到達
http://imageshack.us/photo/my-images/706/91313357.png

メモリ
不足 http://imageshack.us/photo/my-images/825/79083069.png

LiveBytes によって並べ替えられた、生成されたオブジェクトの最終的なスタック:

http://imageshack.us/photo/my-images/546/95529690.png

どんな助け、提案、答えも大歓迎です。

4

2 に答える 2

5

問題は、あなたが呼び出しているように、PreparedStatementそれが であるかのように使用していることです。このメソッドのjavadoc は次のように述べています。StatementaddBatch(string)

注: このメソッドは、PreparedStatement または CallableStatement では呼び出すことができません。

このコメントは JDBC 4.0 で追加されましたが、それ以前はメソッドがオプションであると述べていました。Jaybird でこのメソッドを呼び出すことができるという事実PreparedStatementは、そのためのバグです。Jaybird トラッカーで問題JDBC-288を作成しました。

ここで の原因OutOfMemoryError: JaybirdaddBatch(String)の実装 ( ) で使用すると、実装 ( )内部のリストに追加されます。の場合、を呼び出すと、このリスト内のすべてのステートメントが実行されてからクリアされます。ただし、バッチ パラメーターを使用して最初に準備されたステートメントを実行するようにオーバーライドされます (この例では、PreparedStatement スタイルのバッチを実際に追加することはないため、何もしません)。で追加したステートメントを実行することはありませんが、 のステートメントのリストもクリアしません。これが.PreparedStatementFBPreparedStatementStatementFBStatementFBStatementexecuteBatch()FBPreparedStatementexecuteBatch()addBatch(String)FBStatementOutOfMemoryError

これに基づいて、ソリューションはStatementusingを作成し、cnx.createStatementそれを使用してクエリを実行するか、パラメーター化されたクエリで 1 つ以上のPreparedStatementオブジェクトを使用することでメリットがあるかどうかを調査する必要があります。2 つの個別の PreparedStatements を使用できるように見えますが、100% 確実ではありません。追加の利点は、SQL インジェクションに対する保護と、わずかなパフォーマンスの向上です。

補遺

この問題は、Jaybird 2.2.2 以降で修正されています。

完全な開示: 私は Jaybird / Firebird JDBC ドライバーの開発者です。

于 2012-12-07T18:19:16.647 に答える
1

結果セットを繰り返し処理している間は、バッチ ステートメントを実行しないでください。実行するSQLをコレクションに保存し、結果セットの処理が完了したら、新しいSQLの実行を開始します。すべてが同じトランザクション内で発生する必要がありますか?

于 2012-12-07T00:36:24.710 に答える