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

Kan*_*mar 110 java jdbc

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

  1. 最大打开游标是否与JDBC连接数完全相关,或者它们是否与我们为单个连接创建的语句和结果集对象相关?(我们正在使用连接池)
  2. 有没有办法配置数据库中的语句/结果集对象的数量(如连接)?
  3. 是否建议在单线程环境中使用实例变量statement/resultset对象而不是方法local statement/resultset对象?
  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 
    
    Run Code Online (Sandbox Code Playgroud)
  5. 如果在单个连接对象上多次调用conn.createStatement()和conn.prepareStatement(sql)会发生什么?

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

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

请帮我理解.

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

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

[oracle@db01 ~]$ sqlplus / as sysdba 
Run Code Online (Sandbox Code Playgroud)

然后跑

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';
Run Code Online (Sandbox Code Playgroud)

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

And*_*ock 280

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

常见原因是:

  1. 配置错误

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

      • 增加数据库上的游标数(如果资源允许)或
      • 减少应用程序中的线程数.
  2. 光标泄漏

    • 应用程序未关闭ResultSets(在JDBC中)或游标(在数据库中的存储过程中)
    • 解决方案:游标泄漏是错误; 增加数据库上的游标数量只会延迟不可避免的失败.可以使用静态代码分析,JDBC或应用程序级日志记录以及数据库监视找到泄漏.

背景

本节介绍了游标背后的一些理论以及如何使用JDBC.如果你不需要知道背景,你可以跳过这个并直接进入'消除泄漏'.

什么是光标?

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

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

查找并设置游标数量

该号码通常由DBA在安装时配置.可以在Oracle SQL Developer的Administrator函数中访问当前使用的游标数,最大数量和配置.从SQL可以设置为:

ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

例如,您可以在一个线程中创建连接; 另一个线程可以使用此连接创建PreparedStatement,第三个线程可以处理结果集.唯一的主要限制是,您不能在任何时候在单个PreparedStatement上打开多个ResultSet.请参阅Oracle DB是否支持每个连接的多个(并行)操作?

请注意,数据库提交在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) { }
}
Run Code Online (Sandbox Code Playgroud)

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

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

在Java 7中,Oracle引入了AutoCloseable接口,该接口用一些很好的语法糖取代了大部分Java 6样板.

持有JDBC对象

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

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

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

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

消除泄漏

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

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

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

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

  2. 在运行时:

    1. 可持续性和提交

      1. 如果结果可保存性是ResultSet.CLOSE_CURSORS_OVER_COMMIT,那么当Connection.commit()方法被调用的结果集被关闭.这可以使用Connection.setHoldability()或使用重载的Connection.createStatement()方法设置.
    2. 在运行时记录.

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

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

其他想法

你可以使用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对象的好方法.

  • 如果在循环中创建语句,请确保它在循环中关闭,否则您将最终只关闭最后一个语句. (3认同)

Kan*_*mar 26

我增加了一些理解.

  1. 光标只是一个陈述目标; 它既不是resultSet也不是连接对象.
  2. 但是我们仍然必须关闭结果集来释放一些oracle内存.仍然如果你不关闭不会计入CURSORS的结果集.
  3. 关闭Statement对象也会自动关闭resultset对象.
  4. 将为所有SELECT/INSERT/UPDATE/DELETE语句创建游标.
  5. 可以使用oracle SID识别每个ORACLE数据库实例; 类似地,ORACLE DB可以使用连接SID识别每个连接.两个SID都不同.
  6. 所以ORACLE会话只不过是一个jdbc(tcp)连接; 这只是一个SID.
  7. 如果我们将最大游标设置为500,那么它仅适用于一个JDBC会话/连接/ SID.
  8. 因此,我们可以与其各自的游标(语句)进行许多JDBC连接.
  9. 一旦JVM终止,所有连接/游标将被关闭,或者JDBCConnection被关闭.关于该连接的CURSORS将被关闭.

Loggin as sysdba.

在Putty(Oracle登录):

  [oracle@db01 ~]$ sqlplus / as sysdba
Run Code Online (Sandbox Code Playgroud)

在SqlPlus中:

用户名: sys as sysdba

将session_cached_cursors值设置为0,以便它不会关闭游标.

 alter session set session_cached_cursors=0
 select * from V$PARAMETER where name='session_cached_cursors'
Run Code Online (Sandbox Code Playgroud)

在DB中为每个连接选择现有的OPEN_CURSORS值集

 SELECT max(a.value) as highest_open_cur, p.value as max_open_cur FROM v$sesstat a, v$statname b, v$parameter p WHERE a.statistic# = b.statistic# AND b.name = 'opened cursors current' AND p.name= 'open_cursors'  GROUP BY p.value;
Run Code Online (Sandbox Code Playgroud)

下面是查找具有打开游标值的SID /连接列表的查询.

 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 = 'SCHEMA_NAME_IN_CAPS'
Run Code Online (Sandbox Code Playgroud)

使用以下查询来识别打开游标中的sql

 SELECT oc.sql_text, s.sid 
 FROM v$open_cursor oc, v$session s
 WHERE OC.sid = S.sid
 AND s.sid=1604
 AND OC.USER_NAME ='SCHEMA_NAME_IN_CAPS'
Run Code Online (Sandbox Code Playgroud)

现在调试代码并享受!!! :)


Mir*_*rko 5

像这样更正您的代码:

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()
} 
Run Code Online (Sandbox Code Playgroud)

您确定您真的关闭了 pStatements、连接和结果吗?

要分析打开的对象,您可以实施委托模式,该模式将代码封装在您的声明、连接和结果对象周围。所以你会看到,如果一个对象将成功关闭。

示例: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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

306794 次

最近记录:

7 年,12 月 前