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

标签 java oracle jdbc

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

  • 最大打开游标数是否与 JDBC 连接数完全相关,或者它们是否也与我们为单个连接创建的语句和结果集对象相关? (我们正在使用连接池)
  • 有没有办法配置数据库中语句/结果集对象的数量(如连接)?
  • 是否建议在单线程环境中使用实例变量语句/结果集对象而不是方法本地语句/结果集对象?
  • 在循环中执行准备好的语句会导致此问题吗? (当然,我可以使用 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 
    
  • 如果在单个连接对象上多次调用 conn.createStatement() 和 conn.prepareStatement(sql) 会发生什么?

  • 编辑1:
    6. 使用弱/软引用语句对象是否有助于防止泄漏?

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

    请帮助我理解它。

    解决方案

    在 Oracle DB 中为用户名 -VELU 查找打开的游标

    转到 ORACLE 机器并以 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';
    

    如果可能,请阅读 my answer for more understanding of my solution

    最佳答案

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

    常见原因有:

  • 配置错误
  • 您的应用程序中查询数据库的线程多于 DB 上的游标。一种情况是您的连接和线程池大于数据库上的游标数量。
  • 您有许多开发人员或应用程序连接到同一个数据库实例(其中可能包含许多架构),并且您一起使用了太多连接。
  • 解决方案:
  • Increasing the number of cursors在数据库上(如果资源允许)或
  • 减少应用程序中的线程数。
  • 光标泄漏
  • 应用程序未关闭 ResultSet(在 JDBC 中)或游标(在数据库的存储过程中)
  • 解决方案 :光标泄漏是错误;增加数据库上的游标数量只会延迟不可避免的失败。可以使用 static code analysis 发现泄漏, JDBC或应用程序级日志记录,以及 database monitoring .

  • 背景

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

    什么是游标?

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

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

    查找和设置光标数量

    该编号通常由 DBA 在安装时配置。当前使用的游标数量、最大数量和配置可以在Oracle SQL Developer的管理员功能中访问。 .从 SQL 可以设置为:
    ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
    

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

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

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

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

    请注意,数据库提交发生在连接上,因此该连接上的所有 DML(插入、更新和删除)将一起提交。因此,如果要同时支持多个事务,那么每个并发事务必须至少有一个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,它可能会失败并阻止 Statement 被关闭
  • 我们希望允许 try 主体中引发的任何异常传播给调用者。
    如果您有一个循环,例如,创建和执行语句,请记住关闭循环中的每个语句。

  • 在 Java 7 中,Oracle 引入了 AutoCloseable interface它用一些不错的语法糖替换了大部分 Java 6 样板。

    持有 JDBC 对象

    JDBC 对象可以安全地保存在局部变量、对象实例和类成员中。通常更好的做法是:
  • 使用对象实例或类成员来保存在较长时间内多次重复使用的 JDBC 对象,例如 Connections 和 PreparedStatements
  • 对 ResultSet 使用局部变量,因为它们通常在单个函数的范围内获得、循环然后关闭。

  • 但是,有一个异常(exception):如果您使用 EJB 或 Servlet/JSP 容器,则必须遵循严格的线程模型:
  • 只有应用程序服务器创建线程(用它来处理传入的请求)
  • 只有应用程序服务器创建连接(您从连接池中获得)
  • 在调用之间保存值(状态)时,您必须非常小心。永远不要将值存储在您自己的缓存或静态成员中 - 这在集群和其他奇怪的情况下是不安全的,并且应用程序服务器可能会对您的数据做可怕的事情。而是使用有状态 bean 或数据库。
  • 特别是,永远不要在不同的远程调用上持有 JDBC 对象(Connections、ResultSets、PreparedStatements 等)——让 Application Server 管理它。 Application Server 不仅提供连接池,还缓存您的 PreparedStatements。

  • 消除泄漏

    有许多流程和工具可用于帮助检测和消除 JDBC 泄漏:
  • 在开发过程中 - 尽早捕获错误是迄今为止最好的方法:
  • 开发实践:良好的开发实践应该在软件离开开发人员办公 table 之前减少软件中的错误数量。具体做法包括:
  • Pair programming , 教育那些没有足够经验的人
  • Code reviews因为多只眼睛胜过一只
  • Unit testing这意味着您可以从测试工具中练习任何和所有代码库,这使得重现泄漏变得微不足道
  • 使用 existing libraries用于连接池而不是构建自己的
  • 静态代码分析:使用类似优秀的工具Findbugs执行静态代码分析。这会找出许多没有正确处理 close() 的地方。 Findbugs 有一个 Eclipse 插件,但它也可以一次性独立运行,集成了 Jenkins CI 和其他构建工具
  • 在运行时:
  • 可持有性和提交
  • 如果 ResultSet 的可保持性是 ResultSet.CLOSE_CURSORS_OVER_COMMIT,则在调用 Connection.commit() 方法时关闭 ResultSet。这可以使用 Connection.setHoldability() 或使用重载的 Connection.createStatement() 方法设置。
  • 运行时记录。
  • 将好的日志语句放入您的代码中。这些内容应清晰易懂,以便客户、支持人员和团队成员无需培训即可理解。它们应该简洁,包括打印关键变量和属性的状态/内部值,以便您可以跟踪处理逻辑。良好的日志记录是调试应用程序的基础,尤其是那些已部署的应用程序。
  • 您可以将调试 JDBC 驱动程序添加到您的项目中(用于调试 - 不要实际部署它)。一个例子(我没用过)是 log4jdbc .然后你需要对这个文件做一些简单的分析,看看哪些执行没有相应的关闭。计算打开和关闭应该突出显示是否存在潜在问题
  • 监控数据库。使用 SQL Developer 'Monitor SQL' 功能或 Quest's TOAD 等工具来监控您正在运行的应用程序.监控在 this article 中有描述.在监视期间,您查询打开的游标(例如从表 v$sesstat)并查看它们的 SQL。如果游标的数量在增加,并且(最重要的是)被一个相同的 SQL 语句支配,那么您就知道该 SQL 存在泄漏。搜索您的代码并查看。

  • 其他想法

    你能用 Wea​​kReferences 来处理关闭连接吗?

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

    如果您在构造函数中将 ReferenceQueue 传递给软引用或弱引用,则当对象在发生时(如果它发生了)被 GC 处理时,该对象将被放置在 ReferenceQueue 中。使用这种方法,您可以与对象的终结进行交互,并且您可以在那一刻关闭或终结对象。

    幻像引用有点奇怪;它们的目的只是控制终结,但你永远无法获得对原始对象的引用,因此很难对其调用 close() 方法。

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

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

    关于java.sql.SQLException : - ORA-01000: maximum open cursors exceeded,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12192592/

    相关文章:

    java - 每个上下文的 Log4J

    java - 静态无效类

    java - 错误 org.apache.pig.PigServer - 解析期间出现异常 : Error during parsing. 无法实例化

    sql - 使用子查询而不是表名

    oracle - 查看从数据库中删除的删除表级联约束

    oracle - 我可以在 View 中使用使用 "=>"语法接受参数的函数吗?

    java - 如何获取 java.sql.ResultSet 的大小?

    java - hibernate 乐观锁和集合的问题

    java - 释放 JDBC 资源

    java - 重新连接数据库后如何复用JDBC中的Statement?