我正在用java编写完整的数据库提取程序。数据库是Oracle,它是巨大的。有些表有大约2.6亿条记录。程序应该以特定格式为每个表创建一个文件,因此使用Oracle数据泵等不是一个选项。此外,某些公司安全策略不允许编写PL / SQL过程以在此服务器上为此要求创建文件。我必须使用Java和JDBC。
我面临的问题是,由于某些表的文件很大(~30GB),即使使用20GB的Java堆,我几乎每次都会耗尽内存。在文件大小超过堆大小的文件创建期间,即使使用最激进的GC策略之一,该过程似乎也会挂起。例如,如果文件大小> 20GB和堆大小为20GB,一旦堆利用率达到最大堆大小,它每分钟写入速度减慢2MB左右,并且以此速度,需要几个月才能获得完全提取。
我正在寻找一些方法来克服这个问题。任何帮助将不胜感激。
以下是我所拥有的系统配置的一些细节: Java - JDK1.6.0_14
系统配置 - 在4 X Intel Xeon E7450(6核)@ 2.39GH上运行的RH Enterprise Linux(2.6.18)
RAM - 32GB
数据库Oracle 11g
文件的部分代码如下:
private void runQuery(Connection conn, String query, String filePath,
String fileName) throws SQLException, Exception {
PreparedStatement stmt = null;
ResultSet rs = null;
try {
stmt = conn.prepareStatement(query,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(maxRecBeforWrite);
rs = stmt.executeQuery();
// Write query result to file
writeDataToFile(rs, filePath + "/" + fileName, getRecordCount(
query, conn));
} catch (SQLException sqle) {
sqle.printStackTrace();
} finally {
try {
rs.close();
stmt.close();
} catch (SQLException ex) {
throw ex;
}
}
}
private void writeDataToFile(ResultSet rs, String tempFile, String cnt)
throws SQLException, Exception {
FileOutputStream fileOut = null;
int maxLength = 0;
try {
fileOut = new FileOutputStream(tempFile, true);
FileChannel fcOut = fileOut.getChannel();
List<TableMetaData> metaList = getMetaData(rs);
maxLength = getMaxRecordLength(metaList);
// Write Header
writeHeaderRec(fileOut, maxLength);
while (rs.next()) {
// Now iterate on metaList and fetch all the column values.
writeData(rs, metaList, fcOut);
}
// Write trailer
writeTrailerRec(fileOut, cnt, maxLength);
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
try {
fileOut.close();
} catch (IOException ioe) {
fileOut = null;
throw new Exception(ioe.getMessage());
}
}
}
private void writeData(ResultSet rs, List<TableMetaData> metaList,
FileChannel fcOut) throws SQLException, IOException {
StringBuilder rec = new StringBuilder();
String lf = "\n";
for (TableMetaData tabMeta : metaList) {
rec.append(getFormattedString(rs, tabMeta));
}
rec.append(lf);
ByteBuffer byteBuf = ByteBuffer.wrap(rec.toString()
.getBytes("US-ASCII"));
fcOut.write(byteBuf);
}
private String getFormattedString(ResultSet rs, TableMetaData tabMeta)
throws SQLException, IOException {
String colValue = null;
// check if it is a CLOB column
if (tabMeta.isCLOB()) {
// Column is a CLOB, so fetch it and retrieve first clobLimit chars.
colValue = String.format("%-" + tabMeta.getColumnSize() + "s",
getCLOBString(rs, tabMeta));
} else {
colValue = String.format("%-" + tabMeta.getColumnSize() + "s", rs
.getString(tabMeta.getColumnName()));
}
return colValue;
}
答案 0 :(得分:3)
可能是由于您拨打prepareStatement
的方式,请参阅this question了解类似问题。您不需要可滚动性,ResultSet
将是只读的默认设置,因此只需调用
stmt = conn.prepareStatement(query);
答案 1 :(得分:1)
修改强>: 使用JPA将数据库表映射到Class 现在使用Hibernate以一些可容忍的大小批量加载来自DB的对象集合,并将其序列化为FILE。
答案 2 :(得分:0)
您的算法是否如下所示?这假设DB行和文件中的行之间有直接映射:
// open file for writing with buffered writer.
// execute JDBC statement
// iterate through result set
// convert rs to file format
// write to file
// close file
// close statement/rs/connection etc
尝试使用Spring JDBC Template来简化JDBC部分。
答案 3 :(得分:0)
我相信这在默认的32 MB java堆上必须是可能的。只需获取每一行,将数据保存到文件流,闪存并关闭一次。
答案 4 :(得分:0)
您对maxRecBeforWrite有什么价值?
也许最大记录长度的查询是通过强制JDBC扫描整个结果的记录长度来破坏你的setFetchSize?也许你可以延迟编写标题并注意动态的最大记录大小。