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

Posted

技术标签:

【中文标题】java.sql.SQLException: - ORA-01000: 超过最大打开游标【英文标题】:java.sql.SQLException: - ORA-01000: maximum open cursors exceeded 【发布时间】:2012-08-30 08:13:15 【问题描述】:

我收到 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

【问题讨论】:

你能发布你的完整代码吗?看看你在哪里关闭为for (String language : additionalLangs) 打开的左大括号会很有趣 @ Kanagavelu Sugumar:为什么不在 SO 中问 5 个不同的问题? 这是我发现非常有用的回复:***.com/a/4507507/501113 请看答案是否有用:***.com/questions/34716456/… 为了在 Oracle 中跟踪打开的游标,您可能还需要查看 SYS.V$OPEN_CURSOR 视图。这不仅会为您提供 SID,还会为您提供 SQL 文本。 【参考方案1】:

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

常见原因有:

    配置错误

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

    解决方案:

    数据库上的Increasing the number of cursors(如果资源允许)或 减少应用程序中的线程数。

    光标泄漏

    应用程序未关闭 ResultSet(在 JDBC 中)或游标(在数据库的存储过程中) 解决方案:光标泄漏是错误;增加数据库上的游标数量只会延迟不可避免的故障。可以使用static code analysis、JDBC 或应用程序级日志记录和database monitoring 找到泄漏。

背景

本节介绍游标背后的一些理论以及应如何使用 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?

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

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

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

持有 JDBC 对象

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

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

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

只有应用程序服务器创建线程(用于处理传入请求) 只有应用服务器创建连接(从连接池中获取) 在调用之间保存值(状态)时,必须非常小心。永远不要将值存储在您自己的缓存或静态成员中——这在集群和其他奇怪的情况下是不安全的,并且应用程序服务器可能会对您的数据做可怕的事情。而是使用有状态 bean 或数据库。 特别是,从不在不同的远程调用上保存 JDBC 对象(连接、结果集、PreparedStatements 等) - 让应用程序服务器管理它。 Application Server 不仅提供连接池,还缓存您的 PreparedStatements。

消除泄漏

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

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

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

        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 的“监控 SQL”功能或Quest's TOAD 等工具监控正在运行的应用程序。 this article 中描述了监控。在监视期间,您查询打开的游标(例如从表 v$sesstat)并查看它们的 SQL。如果游标的数量在增加,并且(最重要的是)被一个相同的 SQL 语句支配,那么您就知道该 SQL 存在泄漏。搜索您的代码并查看。

其他想法

你可以使用 Wea​​kReferences 来处理关闭连接吗?

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

如果你将构造函数中的ReferenceQueue传递给软或弱Reference,当对象被GC'ed时,对象被放置在ReferenceQueue中(如果它发生的话)。使用这种方法,您可以与对象的最终确定进行交互,并且您可以在那一刻关闭或最终确定对象。

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

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

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

【讨论】:

如果您在循环中创建语句,请确保它在循环中关闭,否则您最终只会关闭最后一条语句。 谢谢,basiljames。刚刚编辑了答案以添加您提出的观点。 @Andrew Alcock 非常感谢!安德鲁。能否请您也回答第 6 个问题。 @AndrewAlcock 请.. 请.. 请.. 也回答我的第 7 个问题。自从我们的项目以来,我们在负载测试时非常频繁地面临 ORA-01000。你的意见对我来说更有价值。非常感谢! RE: 7 - 您可以尝试使用 grep 等工具进行邻近搜索。当您识别 SQL(选择、插入、更新、删除)时,请查看语句旁边的单词 close() 的接近程度。如果距离比预期的更远,这可能是一种调查缺失位置的方法。 lightboxtechnologies.com/2012/07/27/…【参考方案2】:

我补充一点理解。

    光标仅与语句对象有关;它既不是 resultSet 也不是连接对象。 但我们仍然必须关闭结果集以释放一些 oracle 内存。如果您不关闭不计入 CURSORS 的结果集。 关闭语句对象也会自动关闭结果集对象。 将为所有 SELECT/INSERT/UPDATE/DELETE 语句创建光标。 可以使用 oracle SID 识别每个 ORACLE DB 实例;类似地,ORACLE DB 可以使用连接 SID 来识别每个连接。两个 SID 不同。 所以 ORACLE 会话只不过是一个 jdbc(tcp) 连接;这不过是一个 SID。 如果我们将最大游标设置为 500,则它仅适用于一个 JDBC 会话/连接/SID。 因此,我们可以有许多 JDBC 连接及其各自的游标(语句)数。 一旦 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'

在 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;

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

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

【讨论】:

这是另一个似乎运行良好的查询:***.com/a/2560415/32453【参考方案3】:

像这样更正您的代码:

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");
        
    

【讨论】:

【参考方案4】:

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

如果特定数据源的 Statement Cache Size 设置大约等于或大于 Oracle 数据库最大打开游标计数设置,则所有打开游标都可以被缓存的 SQL 语句使用,这些 SQL 语句保持打开状态WebLogic,导致 ORA-01000 错误。

要解决此问题,请将指向 Oracle 数据库的每个 WebLogic 数据源的 Statement Cache Size 设置减少到明显小于数据库上的最大游标计数设置。

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

【讨论】:

Hibernate 也有一个语句缓存。另见developer.jboss.org/wiki/…【参考方案5】:

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

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

我使用 Spring FrameworkSpring JDBC 作为 dao 层。

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

经过大量的调试和分析,发现其中一个Table中的Indexing, Primary Key and Unique Constraints存在问题我正在执行的查询

我的应用程序试图更新被错误地编入索引。 因此,每当我的应用程序对索引列进行更新查询时,数据库都会尝试根据更新的值进行重新索引。它泄露了游标

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

【讨论】:

【参考方案6】:

我今天遇到了同样的问题 (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/…)。 只是为了澄清,在这些例子中我没有使用 Spring。【参考方案7】:

您是否设置了 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 

【讨论】:

能否请您也回答其他问题? 自动提交不会关闭连接——它只会在执行后立即自动提交每个语句。如果您使用自动提交,则不会从数据库最重要的属性之一——事务中获得价值。您可以考虑改用 NoSQL DB。【参考方案8】:

查询以查找打开的 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

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

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

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

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

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

【讨论】:

【参考方案12】:

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

我发现在某些情况下,即使调用了 statement.close(),该语句也没有关闭。 问题出在我们使用的 Oracle 驱动程序:ojdbc7.jar。该驱动适用于 Oracle 12c 和 11g,与 Oracle 10g 一起使用时似乎存在一些问题,因此我降级到 ojdbc5.jar,现在一切正常。

【讨论】:

【参考方案13】:

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

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

ALTER SYSTEM SET open_cursors = 8000 SCOPE=BOTH;

这立即解决了我的问题。

【讨论】:

这缓解了一些症状,但实际上并没有解决问题。您需要修复您的代码,以便在完成游标后关闭它们。【参考方案14】:

在将准备好的语句缓存大小设置为较大值后,我遇到了这个问题。显然,当准备好的语句保存在缓存中时,游标保持打开状态。

【讨论】:

以上是关于java.sql.SQLException: - ORA-01000: 超过最大打开游标的主要内容,如果未能解决你的问题,请参考以下文章

Oracle DB:java.sql.SQLException:关闭连接

Cause: java.sql.SQLException: 请求的转换无效 ; uncategorized SQLException for SQL []; SQL state [99999]; er

java.sql.SQLException: ORA-00604: 递归 SQL 级别 1 发生错误

Java:Sqlexception:位置不支持的 SQL92 令牌:178

java.sql.SQLException:接近“on”:语法错误问题

java.sql.SQLException:架构“ROOT”不存在