2

我正在处理下面的代码(为清楚起见进行了编辑),这给我在 Oracle 中打开游标带来了一些问题。基本上我正在尝试从数据库中选择数据,并且对于返回的每一行,有 0 行或更多行要选择并附加到记录中的子数据。目前这是通过在填充主数据集以读取子数据时调用另一个函数来实现的。这适用于少于 1000 行的少量行。虽然这是用户将使用的正常操作范围,但他们可能会请求所有行,这些行可能是数千行的 10 行。进行大容量选择会导致ORA-01000:超出最大打开游标错误。如果我运行代码并查询 v$open_cursors ,则可能会看到游标计数不断增加,直到它倒下。

如果我注释掉调用子函数的行,它可以正常工作,v$open_cursors 中的光标计数只会上下波动几个计数。

我注意到主函数将它的连接对象传递给子函数,并认为这可能会导致结果语句和结果集在连接仍然打开时保持打开状态,即使它们已被代码关闭。所以我尝试更改代码,以便每个函数从池中获取它自己的连接并在完成后关闭它,但这对游标没有任何影响。

我可以增加游标的数量,但是 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;
} 
4

4 回答 4

2

JDBC 驱动程序可能会因单个连接上同时存在多个结果集而窒息。我怀疑这会导致 Oracle 的 JDBC 驱动程序出现一些错误行为(我当然已经看到它在其他人身上产生了问题——包括只是关闭你的第一个结果集,而 Oracle 显然没有这样做)。我会更好地连接到标题行,读取所有对象,将它们放入集合中,然后遍历它们并使用单独的结果集读取详细对象。

尽管 JDBC 规范没有规定 JDBC 驱动程序对此的任何义务,但 JDBC-ODBC 桥明确地只允许每个连接一个活动语句,因此其他 JDBC 驱动程序当然可以自由地有类似的限制(例如只有一个打开每个连接的结果集)。

于 2009-09-03T13:37:08.753 回答
1

您可以尝试预先准备主要(“master”)和 sub(“detail”)语句:

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();
    }
}
于 2009-09-03T12:45:02.977 回答
0

哎呀,这看起来像是 1999 年的 PowerBuilder 代码。为子项执行多项选择是一种反模式。您需要在较少调用数据库的情况下执行此操作……这是一种健谈的方式。

由于您在 Oracle 上,因此您可以尝试使用 connect by before 来检索具有父行的子行 - 一次全部完成。这是最好的解决方案。

如果您无法通过先验获得连接,则可以将调用组合到 in(id1,id2,...,idN) 子句中并以块的形式检索它们。

还可以查看结果集上的并发设置。也许你有一个可滚动的结果集?

无论您如何解决它,我都会担心炸毁VM并导致OOM。您需要对搜索结果进行行限制。

于 2009-09-03T12:44:39.853 回答
0

你在使用连接池吗?当您认为关闭它们时,它可能正在缓存一些 PreparedStatements。

要检查您是否属于这种情况,请尝试(暂时)使用未准备好的语句或禁用连接池。

于 2009-09-03T18:02:37.520 回答