java.sql.SQLException:-ORA-01000:已超过最大打开游标


115

我收到了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 
  5. 如果在单个连接对象上多次调用conn.createStatement()和conn.prepareStatement(sql)会发生什么?

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

Edit2: 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';

如果可能的话,请阅读我的答案以进一步了解我的解决方案


您可以发布完整的代码吗?看到你在哪里打开关闭的开幕括号这将是有趣的for (String language : additionalLangs) {
雅各布

// @ Kanagavelu Sugumar:为什么不在SO中问5个不同的问题?
Jayan 2012年

1
这是我发现非常有用的回应:stackoverflow.com/a/4507507/501113
chaotic3quilibrium

请查看答案是否有用:stackoverflow.com/questions/34716456/…–
Manu

为了在Oracle中跟踪打开的游标,您可能还需要看一下SYS.V$OPEN_CURSOR视图。这不仅会给您SID,还会给您SQL文本。
巴斯

Answers:


290

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

常见原因有:

  1. 配置错误

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

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

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

背景

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

什么是游标?

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

数据库实例通常服务于几种不同的模式,许多不同的用户各自具有多个会话。为此,它具有可用于所有模式,用户和会话的固定数量的游标。当所有游标都处于打开状态(使用中)并且要求新游标的请求进入时,请求失败,并出现ORA-010000错误。

查找和设置光标数量

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

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

将JVM中的JDBC与数据库上的游标相关联

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

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

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

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

请注意,数据库提交发生在连接上,因此该连接上的所有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()引发的任何异常:

  • 如果您只关闭ResultSet而没有使用try {} catch {},则它可能会失败并阻止Statement被关闭
  • 我们希望允许尝试主体中引发的任何异常传播到调用方。如果您有一个循环,例如创建和执行语句,请记住关闭循环中的每个语句。

在Java 7中,Oracle引入了AutoCloseable接口,该接口用一些不错的语法糖代替了大多数Java 6样板。

持有JDBC对象

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

  • 使用对象实例或类成员来保存可以在更长的时间内多次重用的JDBC对象,例如Connections和PreparedStatements
  • 将局部变量用于ResultSet,因为通常会在单个函数的范围内获取,循环并关闭它们。

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

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

消除泄漏

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

  1. 在开发过程中-尽早发现错误是迄今为止最好的方法:

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

      1. 配对编程,以教育没有足够经验的人
      2. 代码审查,因为许多眼睛胜过一只眼睛
      3. 单元测试,这意味着您可以使用测试工具来练习所有代码库,从而使重现泄漏变得微不足道
      4. 使用现有的库进行连接池,而不是构建自己的库
    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的TOAD之类的工具监视正在运行的应用程序。本文介绍了监视。在监视期间,您查询打开的游标(例如,从表v $ sesstat中)并查看其SQL。如果游标的数量在增加,并且(最重要的是)由一个相同的SQL语句控制,则您知道该SQL泄漏。搜索您的代码并查看。

其他想法

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

弱引用和软引用是允许您以某种方式引用对象的方法,该方式允许JVM在它认为合适的任何时间(如果没有针对该对象的强大引用链)对引用对象进行垃圾收集。

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

幻像引用有些古怪;它们的目的只是为了控制完成,但是您永远无法获得对原始对象的引用,因此很难在其上调用close()方法。

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

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


3
如果在循环中创建语句,请确保在循环中将其关闭,否则最终将仅关闭最后一条语句。
basiljames 2012年

谢谢你,巴蕾丝。只需编辑答案即可添加您的观点。
安德鲁·阿尔考克

@安德鲁·阿尔科克非常感谢!安德鲁。您能也请回答6号吗?
Kanagavelu Sugumar 2012年

@ .AndrewAlcock请..请..请..也回答我的第七个问题。由于我们的项目,我们在负载测试时经常面对ORA-01000。您的投入对我来说更有价值。在此先感谢一吨!
Kanagavelu Sugumar 2012年

RE:7-您可以尝试使用grep之类的工具进行邻近搜索。识别SQL(选择,插入,更新,删除)后,请查看该语句旁边单词close()的附近。如果距离比预期的要远,那可能是一种调查缺少的地方的方法。lightboxtechnologies.com/2012/07/27/...
太阳

28

我要补充一些理解。

  1. 游标仅与语句目标有关。它既不是resultSet也不是连接对象。
  2. 但是,我们仍然必须关闭结果集以释放一些Oracle内存。不过,如果您不关闭结果集,该结果集将不会计入CURSORS。
  3. 关闭语句对象也会自动关闭结果集对象。
  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将被关闭。

登录为sysdba。

在Putty中(Oracle登录):

  [oracle@db01 ~]$ sqlplus / as sysdba

在SqlPlus中:

用户名: sys as sysdba

将session_cached_cursors值设置为0,以使其不会关闭游标。

 alter session set session_cached_cursors=0
 select * from V$PARAMETER where name='session_cached_cursors'

在数据库中为每个连接选择现有的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;

以下是查找带有打开的游标值的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'

使用以下查询在打开的游标中标识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'

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


1
这是另一个似乎运行良好的查询:stackoverflow.com/a/2560415/32453
rogerdpack 2013年

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,连接和结果吗?

要分析打开的对象,您可以使用委托模式,该模式将代码包装在状态,连接和结果对象周围。因此,您将看到对象是否成功关闭。

一个示例: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");
        }
    }
}

3

如果您的应用程序是在Oracle WebLogic上作为应用程序服务器运行的Java EE应用程序,则此问题的可能原因是WebLogic中的“ 语句缓存大小”设置。

如果特定数据源的“语句缓存大小”设置大约等于或大于Oracle数据库最大打开游标计数设置,则所有打开的游标都可以被WebLogic保持打开状态的缓存SQL语句占用,从而导致在ORA-01000错误中。

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

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


1
Hibernate也有一个Statement缓存。另请参阅developer.jboss.org/wiki/…–
皮诺

3

我也曾经遇到过这个问题。下面的异常曾经来过

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

我当时将Spring FrameworkSpring JDBC一起用于dao层。

我的应用程序过去曾经以某种方式泄漏游标,几分钟后,它曾给我这个异常。

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

我的应用程序试图更新被错误索引。因此,每当我的应用程序在索引列上命中更新查询时,数据库就会尝试根据更新后的值进行重新索引。它泄漏了光标

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


2

我今天遇到了同样的问题(ORA-01000)。我在try {}中有一个for循环,可以在Oracle DB中多次执行SELECT语句(每次更改一个参数),而在finally {}中,我的代码照常关闭了Resultset,PreparedStatement和Connection 。但是,一旦达到特定数量的循环(1000),我就会收到有关打开的游标过多的Oracle错误。

根据上面的安德鲁·阿尔科克(Andrew Alcock)的帖子,我进行了更改,以便循环内,在获取数据之后且再次循环之前,我关闭了每个结果集和每个语句,从而解决了问题。

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


这似乎不正确。Spring文档负责关闭ResultSets(docs.spring.io/spring/docs/current/spring-framework-reference/…)。
Ryan

只是为了澄清起见,在这些示例中,我没有使用Spring。
Kinnison84 '18

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 

您能同时回答其他问题吗?
Kanagavelu Sugumar 2012年

2
自动提交不会关闭连接-它只会在执行后立即自动提交每个语句。如果使用自动提交,则不会从数据库最重要的属性之一-事务中获得价值。您可以考虑改为使用NoSQL DB。
Andrew Alcock 2012年

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

1

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



0

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

我们通过使用查询来检查打开的游标的数量发现了这一点在运行代码时,逐步调试器并有选择地注释掉内容,。

关于每个新引用为何打开另一个光标的原因-所讨论的实体具有映射到其上的其他实体的集合,我认为这与它有关(也许不仅如此,而且还与我们配置提取方式和缓存设置)。Hibernate本身存在无法关闭的错误打开的游标的,尽管看起来这些错误已在更高版本中修复。

由于我们实际上并不需要那么多对同一实体的重复引用,因此解决方案是停止创建并保留所有这些冗余引用。一旦我们做到了,那时候就出问题了。


0

我在WildFly和Tomcat中连接到Oracle 10g的数据源遇到此问题。

我发现在某些条件下,即使调用statement.close(),该语句也不会关闭。问题出在我们使用的Oracle驱动程序上:ojdbc7.jar。该驱动程序旨在用于Oracle 12c和11g,与Oracle 10g一起使用时似乎存在一些问题,因此我将其降级为ojdbc5.jar,现在一切运行正常。


0

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

为了解决这个问题,我只是登录到oracle db并在查询下面运行:

ALTER SYSTEM SET open_cursors = 8000 SCOPE = BOTH;

这立即解决了我的问题。


这缓解了一些症状,但并未真正解决问题。您需要修复代码,以便在完成游标操作后关闭游标。
APC
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.