我正在编写下面的代码,(为了清晰起见而编辑),这给了我在Oracle中打开游标的一些问题。 基本上我试图从数据库中选择数据,并且对于返回的每一行,要选择0行或更多行子数据并将其附加到记录中。 目前,这是通过在填充主数据集以读取子数据的同时调用另一个函数来实现的。这适用于少量行的少量行。虽然这是用户将使用的正常操作范围,但是他们可能会请求所有行,这些行可能是数十万行的数量级。执行大量选择会导致 ORA-01000:最大打开游标超出错误。如果我运行代码并查询v $ open_cursors,则可以看到光标计数计时,直到它崩溃。
如果我注释掉调用子函数的行它可以正常工作,v $ open_cursors中的光标数就会上下波动几次。
我注意到main函数将它的连接对象传递给sub函数,并认为这可能导致结果语句和结果集在连接仍然打开时保持打开,即使它们被代码关闭。所以我尝试更改代码,以便每个函数从池中获取它自己的连接并在完成后关闭它,但这对游标没有任何影响。
我可以增加光标的数量,但是a)只是掩盖了问题,b)我必须坚持到一个愚蠢的高数字才能使它工作,并且c)我不想释放有缺陷的代码!
但是我已经完成了如何让代码释放游标的想法。
public ArrayList getCustomerSearchResult(Connection con) throws AnException {
ResultSet rst = null;
PreparedStatement stmt = null;
ArrayList resultList = new ArrayList();
String sql = "---- The search SQL string --- ";
try {
stmt = con.prepareStatement(sql);
rst = stmt.executeQuery();
while(rst.next()) {
DataDTO data = new DataDTO();
data.setSomeData(rst.getString("...."));
// ##### This call is where the problem lies #####
data.setSomeSubDataAsAnArrayList(getSubDataForThisRow(data.getId(), con));
resultList.add(data);
}
} catch(Exception e) {
throw new AnException("Error doing stuff", e);
} finally{
try{
rst.close();
stmt.close();
rst = null;
stmt = null;
}catch(Exception ex){
throw new AnException("Error doing stuff", ex);
}
}
return resultList;
}
public ArrayList getSubDataForThisRow(String Id, Connection con) throws AnException {
ResultSet rst = null;
PreparedStatement stmt = null;
ArrayList resultList = new ArrayList();
String sql = "---- The search SQL string --- ";
try {
stmt = con.prepareStatement(sql);
stmt.setString(1, Id);
rst = stmt.executeQuery();
while(rst.next()) {
SubDataDTO data = new SubDataDTO();
data.setSomeData(rst.getString("...."));
resultList.add(data);
}
} catch(Exception e) {
throw new AnException("Error!", e);
} finally{
try{
rst.close();
stmt.close();
rst = null;
stmt = null;
}catch(Exception ex){
throw new AnException("Error!", ex);
}
}
return resultList;
}
答案 0 :(得分:2)
JDBC驱动程序可以阻止在单个连接上同时进行多个结果集。我怀疑这会导致甲骨文的JDBC驱动程序出现一些错误的行为(我当然看到它在其他问题上产生问题 - 包括关闭你的第一个结果集,而Oracle显然没有这样做)。我会更好地获得与标题行的连接,读取所有对象,将它们放入Collection中,然后迭代它们并使用单独的结果集读取详细信息对象。
虽然JDBC规范没有说明JDBC驱动程序的任何义务,但JDBC-ODBC桥显式地只允许每个连接一个活动语句,因此其他JDBC驱动程序当然可以自由地具有类似的限制(例如每个连接只有一个打开的结果集。)
答案 1 :(得分:1)
您可以尝试事先准备主要(“主”)和子(“详细”)语句:
PreparedStatement masterStatement = masterConnection.prepareStatement("...");
PreparedStatement detailStatement = detailConnection.prepareStatement("SELECT ... WHERE something = ?");
ResultSet masterResults = masterStatement.executeQuery();
while (masterResults.next()) {
detailStatement.setInt(1, ...);
ResultSet detailResults = detailStatement.executeQuery();
try {
while (detailResults.next()) {
}
} finally {
detailResults.close();
}
}
答案 2 :(得分:0)
由于您使用的是Oracle,因此您可以尝试在使用父行检索子行之前使用connect - 所有这些都是一次性的。这是最好的解决方案。
如果您之前无法获得连接,则可以将调用组合为in(id1,id2,...,idN)子句并以块的形式检索它们。
还可以查看结果集上的并发设置。也许你有一个可滚动的结果集?
无论你如何解决它,我都会担心会烧掉虚拟机并获得OOM。您需要搜索结果的行限制。
答案 3 :(得分:0)
您使用的是连接池吗?当您认为关闭它们时,它可能会缓存一些PreparedStatements。
要检查您是否处于这种情况,请尝试(暂时)使用未准备好的语句或禁用连接池。