java.sql.SQLException: - ORA-01000:超出最大打开游标数

时间:2012-08-30 08:13:15

标签: java jdbc

我收到了ORA-01000 SQL异常。所以我有一些相关的问题。

  1. 最大打开游标是否与JDBC连接数完全相关,或者它们是否与我们为单个连接创建的语句和结果集对象相关? (我们正在使用连接池)
  2. 有没有办法在数据库中配置语句/结果集对象的数量(比如连接)?
  3. 建议在单线程环境中使用实例变量语句/结果集对象而不是方法本地语句/结果集对象吗?
  4. 在循环中执行预准备语句会导致此问题吗? (当然,我本可以使用sqlBatch)注意:一旦循环结束,pStmt就会关闭。

    { //method try starts  
      String sql = "INSERT into TblName (col1, col2) VALUES(?, ?)";
      pStmt = obj.getConnection().prepareStatement(sql);
      pStmt.setLong(1, subscriberID);
      for (String language : additionalLangs) {
        pStmt.setInt(2, Integer.parseInt(language));
        pStmt.execute();
      }
    } //method/try ends
    
    { //finally starts
       pStmt.close()
    } //finally ends 
    
  5. 如果在单个连接对象上多次调用conn.createStatement()和conn.prepareStatement(sql),会发生什么?

  6. Edit1: 6.使用Weak / Soft参考语句对象是否有助于防止泄漏?

    Edit2:  1.有什么办法,我可以在我的项目中找到所有缺失的“statement.close()”吗?我知道这不是内存泄漏。但我需要找一个符合垃圾收集条件的语句引用(不执行close())?有什么工具可用?或者我必须手动分析它?

    请帮我理解。

    解决方案

    在Oracle DB中查找用户名为-VELU

    的已打开游标

    转到ORALCE机器并以sysdba启动sqlplus。

    [oracle@db01 ~]$ sqlplus / as sysdba 
    

    然后运行

    SELECT   A.VALUE,
        S.USERNAME,
        S.SID,
        S.SERIAL#
      FROM V$SESSTAT A,
        V$STATNAME B,
        V$SESSION S
      WHERE A.STATISTIC# = B.STATISTIC#
        AND S.SID        = A.SID
        AND B.NAME       = 'opened cursors current'
        AND USERNAME     = 'VELU';
    

    如果可能,请在最后阅读我的答案。

12 个答案:

答案 0 :(得分:273)

ORA-01000,最大打开游标错误,是Oracle数据库开发中极为常见的错误。在Java的上下文中,当应用程序尝试打开更多ResultSet而不是数据库实例上配置的游标时,会发生这种情况。

常见原因是:

  1. 配置错误

    • 您的应用程序中查询数据库的线程数多于数据库上的游标数。一种情况是您的连接和线程池大于数据库上的游标数。
    • 您有许多开发人员或应用程序连接到同一个数据库实例(可能包含许多模式),并且您使用的连接太多了。
    • 解决方案:

  2. 光标泄漏

    • 应用程序未关闭ResultSets(在JDBC中)或游标(在数据库的存储过程中)
    • 解决方案:游标泄漏是错误;增加数据库上的游标数量只会延迟不可避免的失败。可以使用static code analysisJDBC或应用程序级日志记录以及database monitoring找到泄漏。
  3. 背景

    本节介绍了游标背后的一些理论以及如何使用JDBC。如果您不需要了解背景信息,可以跳过此步骤直接进入“消除泄漏”。

    什么是光标?

    游标是数据库中保存查询状态的资源,特别是读者在ResultSet中的位置。每个SELECT语句都有一个游标,PL / SQL存储过程可以打开并使用所需数量的游标。您可以在Orafaq上找到有关游标的更多信息。

    数据库实例通常提供多个不同的架构,许多不同的用户,每个多个会话。为此,它具有可用于所有模式,用户和会话的固定数量的游标。当所有游标都打开(正在使用中)并且请求进入需要新游标时,请求将失败,并显示ORA-010000错误。

    查找并设置光标数

    该号码通常由DBA在安装时配置。可以在Oracle SQL Developer中的管理员功能中访问当前使用的游标数,最大数量和配置。从SQL可以设置:

    ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
    

    将JVM中的JDBC与DB上的游标相关联

    下面的JDBC对象与以下数据库概念紧密结合:

    • JDBC Connection 是数据库会话的客户端表示,并提供数据库事务。连接只能在任何时候打开一个事务(但事务可以嵌套)
    • 数据库上的单个游标支持JDBC ResultSet 。在ResultSet上调用close()时,将释放游标。
    • JDBC CallableStatement 在数据库上调用存储过程,通常用PL / SQL编写。存储过程可以创建零个或多个游标,并且可以将游标作为JDBC ResultSet返回。

    JDBC是线程安全的:在线程之间传递各种JDBC对象是完全可以的。

    例如,您可以在一个线程中创建连接;另一个线程可以使用此连接创建PreparedStatement,第三个线程可以处理结果集。唯一的主要限制是,您不能在任何时候在单个PreparedStatement上打开多个ResultSet。见Does Oracle DB support multiple (parallel) operations per connection?

    请注意,数据库提交在Connection上发生,因此该连接上的所有DML(INSERT,UPDATE和DELETE')将一起提交。因此,如果要同时支持多个事务,则每个并发事务必须至少有一个Connection。

    关闭JDBC对象

    执行ResultSet的典型示例是:

    Statement stmt = conn.createStatement();
    try {
        ResultSet rs = stmt.executeQuery( "SELECT FULL_NAME FROM EMP" );
        try {
            while ( rs.next() ) {
                System.out.println( "Name: " + rs.getString("FULL_NAME") );
            }
        } finally {
            try { rs.close(); } catch (Exception ignore) { }
        }
    } finally {
        try { stmt.close(); } catch (Exception ignore) { }
    }
    

    注意finally子句如何忽略close()引发的任何异常:

    • 如果您只是在没有try {} catch {}的情况下关闭ResultSet,则可能会失败并阻止语句被关闭
    • 我们希望允许在try主体中引发的任何异常传播给调用者。 如果您有循环,例如,创建和执行语句,请记住关闭循环中的每个语句。

    在Java 7中,Oracle引入了AutoCloseable interface,它用一些很好的语法糖代替了大部分Java 6样板。

    持有JDBC对象

    JDBC对象可以安全地保存在局部变量,对象实例和类成员中。通常更好的做法是:

    • 使用对象实例或类成员来保存在较长时间内多次重复使用的JDBC对象,例如Connections和PreparedStatements
    • 对ResultSet使用局部变量,因为这些变量通常在单个函数的范围内获取,循环然后关闭。

    但是有一个例外:如果您使用的是EJB或Servlet / JSP容器,则必须遵循严格的线程模型:

    • 只有Application Server创建线程(用于处理传入请求)
    • 只有Application Server会创建连接(您从连接池中获取)
    • 在通话之间保存值(状态)时,您必须非常小心。永远不要将值存储在您自己的缓存或静态成员中 - 这在群集和其他奇怪的条件下是不安全的,并且Application Server可能会对您的数据造成可怕的影响。而是使用有状态bean或数据库。
    • 特别是,从不通过不同的远程调用保存JDBC对象(Connections,ResultSets,PreparedStatements等) - 让Application Server管理它。 Application Server不仅提供连接池,还会缓存PreparedStatements。

    消除泄漏

    有许多流程和工具可用于帮助检测和消除JDBC泄漏:

    1. 在开发过程中 - 早期捕获bug是迄今为止最好的方法:

      1. 开发实践:良好的开发实践应该在软件离开开发人员的桌面之前减少其中的错误数量。具体做法包括:

        1. Pair programming,教育没有足够经验的人
        2. Code reviews因为很多人的眼睛比一个人好。
        3. Unit testing这意味着您可以通过一个使再现泄漏变得微不足道的测试工具来运用您的任何和所有代码库
        4. 使用existing libraries进行连接池而非构建自己的
      2. 静态代码分析:使用优秀Findbugs之类的工具执行静态代码分析。这会占用许多未正确处理close()的地方。 Findbugs有一个Eclipse插件,但它也可以单独运行,可以集成到Jenkins CI和其他构建工具中

    2. 在运行时:

      1. 可持续性和提交

        1. 如果ResultSet可保持性为ResultSet.CLOSE_CURSORS_OVER_COMMIT,则在调用Connection.commit()方法时将关闭ResultSet。这可以使用Connection.setHoldability()或使用重载的Connection.createStatement()方法设置。
      2. 在运行时记录。

        1. 在您的代码中添加好的日志语句。这些应该清晰易懂,以便客户,支持人员和团队成员无需培训即可理解。它们应该简洁并包括打印关键变量和属性的状态/内部值,以便您可以跟踪处理逻辑。良好的日志记录是调试应用程序的基础,尤其是已部署的应用程序。
        2. 您可以向项目添加调试JDBC驱动程序(用于调试 - 实际上不要部署它)。一个例子(我没有用过它)是log4jdbc。然后,您需要对此文件进行一些简单的分析,以查看哪些执行没有相应的关闭。如果存在潜在问题,则计算开启和关闭应突出显示

          1. 监控数据库。使用SQL Developer' Monitor SQL'等工具监控正在运行的应用程序。功能或Quest's TOAD。监控在this article中描述。在监视期间,您查询打开的游标(例如,从表v $ sesstat)并查看其SQL。如果游标数量在增加,并且(最重要的是)由一个相同的SQL语句控制,那么您就知道该SQL存在泄漏。搜索您的代码并进行审核。
    3. 其他想法

      您可以使用WeakReferences来处理关闭连接吗?

      弱引用和软引用是允许您以允许JVM在其认为合适的任何时间垃圾收集引用的方式引用对象的方式(假设该对象没有强引用链)。

      如果将构造函数中的ReferenceQueue传递给soft或weak Reference,则当对象发生GC(如果它发生)时,该对象将被放置在ReferenceQueue中。使用这种方法,您可以与对象的最终化进行交互,然后您可以在此时关闭或完成对象。

      幽灵参考有点怪异;它们的目的只是控制最终化,但是你永远无法获得对原始对象的引用,因此很难在它上面调用close()方法。

      但是,尝试控制GC运行的时间很少是一个好主意(Weak,Soft和PhantomReferences让您知道事后该对象已为GC加入队列)。实际上,如果JVM中的内存量很大(例如-Xmx2000m),您可能从不 GC对象,您仍然会遇到ORA-01000。如果JVM内存相对于程序的要求很小,您可能会发现ResultSet和PreparedStatement对象在创建后立即被GCed(在您可以读取它们之前),这可能会使您的程序失败。

      TL; DR:弱引用机制不是管理和关闭Statement和ResultSet对象的好方法。

答案 1 :(得分:4)

像这样纠正您的代码:

try
{ //method try starts  
  String sql = "INSERT into TblName (col1, col2) VALUES(?, ?)";
  pStmt = obj.getConnection().prepareStatement(sql);
  pStmt.setLong(1, subscriberID);
  for (String language : additionalLangs) {
    pStmt.setInt(2, Integer.parseInt(language));
    pStmt.execute();
  }
} //method/try ends
finally
{ //finally starts
   pStmt.close()
} 

你确定,你真的关闭了你的pStatements,连接和结果吗?

要分析打开的对象,您可以实现委托者模式,该模式将代码包装在您的statemant,connection和result对象周围。所以你会看到,如果一个物体成功关闭。

示例:pStmt = obj。 getConnection ()。prepareStatement(sql);

    class obj{ 

    public Connection getConnection(){
    return new ConnectionDelegator(...here create your connection object and put it into ...);

    } 
}


class ConnectionDelegator implements Connection{
    Connection delegates;

    public ConnectionDelegator(Connection con){
       this.delegates = con;
    }

    public Statement prepareStatement(String sql){
        return delegates.prepareStatement(sql);
    }

    public void close(){
        try{
           delegates.close();
        }finally{
           log.debug(delegates.toString() + " was closed");
        }
    }
}

答案 2 :(得分:3)

如果您的应用程序是在Oracle WebLogic上作为应用程序服务器运行的Java EE应用程序,则可能导致此问题的原因是WebLogic中的Statement Cache Size设置。

如果特定数据源的“语句高速缓存大小”设置大约等于或大于Oracle数据库最大打开游标数设置,则所有打开的游标都可以由保持打开的高速缓存的SQL语句使用WebLogic,导致ORA-01000错误。

要解决此问题,请将指向Oracle数据库的每个WebLogic数据源的“语句高速缓存大小”设置减少到远远小于数据库上的最大游标数设置。

在WebLogic 10管理控制台中,可以在“服务”(左侧导航)>中找到每个数据源的“语句缓存大小”设置。数据源> (个人数据源)>连接池选项卡。

答案 3 :(得分:2)

我今天遇到了同样的问题(ORA-01000)。我在try {}中有一个for循环,多次在Oracle DB中执行一个SELECT语句(每次都更改一个参数),而在finally {}中,我让我的代码像往常一样关闭Resultset,PreparedStatement和Connection 。但是当我达到特定数量的循环(1000)时,我得到了关于太多打开游标的Oracle错误。

基于Andrew Alcock的帖子,我做了更改,以便循环中,我在获取数据之后关闭每个结果集和每个语句,然后再次循环,这解决了问题。

另外,在另一个Oracle DB(ORA-01000)的插入语句的另一个循环中出现了完全相同的问题,这次是在300个语句之后。同样,它以相同的方式解决,因此PreparedStatement或ResultSet或两者都被计为开放游标,直到它们被关闭。

答案 4 :(得分:2)

我也遇到过这个问题。以下的例外情况已经来了

java.sql.SQLException: - ORA-01000: maximum open cursors exceeded

我使用 Spring Framework Spring JDBC 作为dao图层。

我的应用程序曾经以某种方式泄漏游标,几分钟左右后,它曾经给我这个例外。

经过大量的彻底调试和分析,我发现在其中一个中存在索引,主键和唯一约束的问题我正在执行查询

我的应用程序正在尝试更新错误索引。 因此,每当我的应用程序在索引列上访问更新查询时,数据库就会尝试根据更新的值执行重建索引。它正在泄漏游标

我能够通过对用于搜索查询的列进行适当的索引并在需要的地方应用适当的约束来解决问题。

答案 5 :(得分:1)

你设置了autocommit = true吗?如果没有试试这个:

{ //method try starts  
    String sql = "INSERT into TblName (col1, col2) VALUES(?, ?)";
    Connection conn = obj.getConnection()
    pStmt = conn.prepareStatement(sql);

    for (String language : additionalLangs) {
        pStmt.setLong(1, subscriberID);
        pStmt.setInt(2, Integer.parseInt(language));
        pStmt.execute();
        conn.commit();
    }
} //method/try ends { 
    //finally starts
    pStmt.close()
} //finally ends 

答案 6 :(得分:1)

查询以查找打开的sql。

SELECT s.machine, oc.user_name, oc.sql_text, count(1) 
FROM v$open_cursor oc, v$session s
WHERE oc.sid = s.sid
and S.USERNAME='XXXX'
GROUP BY user_name, sql_text, machine
HAVING COUNT(1) > 2
ORDER BY count(1) DESC

答案 7 :(得分:0)

使用批处理将减少开销。有关示例,请参阅以下链接: http://www.tutorialspoint.com/jdbc/jdbc-batch-processing.htm

答案 8 :(得分:0)

在我们的例子中,我们使用的是Hibernate,并且我们有很多变量引用相同的Hibernate映射实体。我们在循环中创建并保存这些引用。每个引用打开一个光标并保持打开状态。

我们在运行代码时使用query to check the number of open cursors发现了这一点,逐步调试并有选择地注释掉了。

至于为什么每个新引用打开另一个游标 - 有问题的实体有映射到它的其他实体的集合,我认为这与它有关(可能不仅仅是这个,而是与我们如何配置获取模式和缓存设置)。 Hibernate本身有bugs around failing to close个开放游标,虽然它们看起来已经在以后的版本中得到修复。

由于我们实际上并不需要对同一实体进行如此多的重复引用,因此解决方案是停止创建并保留所有这些冗余引用。一旦我们在离开时就解决了这个问题。

答案 9 :(得分:0)

当您使用连接池时,主要会发生此问题,因为当您关闭连接时,连接将返回到连接池,并且与该连接关联的所有游标永远不会关闭,因为与数据库的连接仍处于打开状态。 因此,一种替代方案是减少池中连接的空闲连接时间,因此每当连接空闲连接10秒时,连接到数据库将关闭并创建新连接以放入池中。

答案 10 :(得分:0)

我在WildFly和Tomcat中使用我的数据源时遇到了这个问题,连接到Oracle 10g。

我发现在某些条件下,即使调用了statement.close(),语句也没有关闭。 问题在于我们使用的Oracle驱动程序:ojdbc7.jar。这个驱动程序适用于Oracle 12c和11g,当与Oracle 10g一起使用时似乎有一些问题,所以我降级到ojdbc5.jar,现在一切运行正常。

答案 11 :(得分:0)

我遇到了同样的问题,因为我正在查询db超过1000次迭代。 我已经在我的代码中使用了try和finally。但仍然有错误。

为了解决这个问题,我刚登录到oracle db并运行以下查询:

ALTER SYSTEM SET open_cursors = 8000 SCOPE = BOTH;

这立即解决了我的问题。