gpt4 book ai didi

java - 使用 Java SE 的嵌入式 SQL Firebird 批量更新 OutOfMemoryError

转载 作者:塔克拉玛干 更新时间:2023-11-01 22:50:37 26 4
gpt4 key购买 nike

我在使用嵌入式 firebird 数据库引擎和 Java SE 时遇到了一个大问题。我目前正在开发一个过滤工具,供用户过滤掉数据。所以我做了两个过滤选项,用户可以选择一个或两个:

  1. 从黑名单中过滤掉(黑名单由用户控制)。
  2. 根据记录了所有上传和过滤掉的每条记录的庞大列表进行过滤。

用户上传的数据以纯文本逗号或 token 分隔,如下所示:

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

当我将它上传到数据库时,我为每个过滤器添加一个标志

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

因此,当涉及到第二个过滤器时,程序在第一次运行软件时有一个主要的空表,然后用第一次上传的所有记录填充该表。用户上传几次文本后,主表将有如下表的唯一记录:

 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

如果在最近三天内数据条件事件达到了四个以上,用户表将标记过滤器 B。注意每个日期旁边都有一个整数。

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

两个更新都在一个事务中,问题是当用户上传超过 800,000 条记录时,我的程序在 while 循环中抛出以下异常。我使用 StringBuilder 解析和追加方法在可变日期字符串上获得最大性能。

java.lang.OutOfMemoryError: Java 堆空间

这是我的代码,我用了五天:

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 异常:

50MB
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

我们将不胜感激任何帮助、建议和回答。

最佳答案

问题是您正在使用 PreparedStatement,就好像它是一个 Statement,因为您正在调用 addBatch(string)javadoc of this method说:

Note:This method cannot be called on a PreparedStatement or CallableStatement.

这个注释是 JDBC 4.0 添加的,之前它说该方法是可选的。 Jaybird 允许您在 PreparedStatement 上调用此方法的事实因此是一个错误:我创建了问题 JDBC-288在 Jaybird 追踪器中。

现在是 OutOfMemoryError 的原因:当您在 Jaybird 的 PreparedStatement 实现上使用 addBatch(String) 时(FBPreparedStatement ),它被添加到 Statement 实现 (FBStatement) 的内部列表中。对于 FBStatement,当您调用 executeBatch() 时,它将执行此列表中的所有语句,然后将其清除。在 FBPreparedStatement 中,executeBatch() 被覆盖以执行带有批处理参数的最初准备好的语句(在您的示例中它不会执行任何操作,因为您从未真正添加 PreparedStatement-样式批处理)。它永远不会执行您使用addBatch(String) 添加的语句,但它也不会清除FBStatement 中的语句列表,这很可能是您的 OutOfMemoryError 的原因。

基于此,解决方案应该是使用 cnx.createStatement 创建一个 Statement 并使用它来执行您的查询,或者调查您是否可以从中受益或多个带有参数化查询的 PreparedStatement 对象。看起来您应该能够使用两个单独的 PreparedStatements,但我不是 100% 确定;额外的好处是防止 SQL 注入(inject)和轻微的性能改进。

附录

此问题已修复 since Jaybird 2.2.2

完全披露:我是 Jaybird/Firebird JDBC 驱动程序的开发人员。

关于java - 使用 Java SE 的嵌入式 SQL Firebird 批量更新 OutOfMemoryError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13754898/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com