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

我得到一个ORA-01000 SQLexception。 所以我有一些相关的疑问。

  1. 最大打开的游标是否与JDBC连接的数量完全相关,还是与我们为单个连接创build的语句和结果集对象有关? (我们正在使用连接池)
  2. 有没有办法configuration数据库中的语句/结果集对象的数量(如连接)?
  3. 在单线程环境中使用实例variables语句/结果集对象而不是方法本地语句/结果集对象是否明智?
  4. 在循环中执行预处理语句是否会导致此问题? (当然,我可以使用sqlBatch)注意:一旦循环结束,pStmt被closures。

    { //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)会发生什么情况?

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

编辑2: 1.有什么办法,我可以find我的项目中所有缺less的“statement.close()”? 我明白这不是内存泄漏。 但是我需要find一个声明引用(其中不执行close())符合垃圾回收的条件吗? 有什么工具可用? 还是我必须手动分析?

请帮我理解一下。

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

转到ORALCE机器,并启动sqlplus作为sysdba。

 [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'; 

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

ORA-01000,最大开放游标错误,在Oracle数据库开发中是一个非常常见的错误。 在Java的上下文中,当应用程序尝试打开更多的ResultSets时,会发生比在数据库实例上configuration了游标的情况。

常见的原因是:

  1. configuration错误

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

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

    • 应用程序不closuresResultSet(在JDBC中)或游标(在数据库上的存储过程中)
    • 解决scheme :游标泄漏是错误; 增加数据库上光标的数量只是延迟了不可避免的失败。 可以使用静态代码分析 , JDBC或应用程序级别的日志logging和数据库监视来find泄漏。

背景

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

什么是游标?

游标是数据库上的一个资源,它保存查询的状态,尤其是读者在ResultSet中的位置。 每个SELECT语句都有一个游标,PL / SQL存储过程可以根据需要打开并使用尽可能多的游标。 你可以在Orafaqfind关于游标的更多信息。

数据库实例通常服务于几个不同的模式 ,许多不同的用户都有多个会话 。 为此,它为所有模式,用户和会话提供固定数量的游标。 当所有的游标都被打开(使用中),并且请求进来,需要一个新的游标,请求失败与ORA-010000错误。

查找并设置游标的数量

该数字通常由DBA在安装时configuration。 当前正在使用的游标数量,最大数量和configuration可以在Oracle SQL Developer的pipe理员function中访问。 从SQL可以设置:

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

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

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

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

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

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

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

closuresJDBC对象

执行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()引发的任何exception:

  • 如果只closuresResultSet而没有try {} catch {},则可能会失败并阻止Statement被closures
  • 我们希望允许在尝试主体中引发的任何exception传播给调用者。 如果你有一个循环,例如创build和执行语句,记得closures循环中的每个语句。

在Java 7中,Oracle引入了AutoCloseable接口 ,该接口将大部分Java 6样板replace为一些很好的语法糖。

持有JDBC对象

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

  • 使用对象实例或类成员来保存长时间重复使用多次的JDBC对象,例如Connections和PreparedStatements
  • 结果集使用局部variables,因为这些variables是获取,循环,然后closures,通常在一个函数的范围内。

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

  • 只有Application Server创build线程(用来处理传入的请求)
  • 只有Application Server创build连接(您从连接池获取)
  • 在调用之间保存值(状态)时,必须非常小心。 切勿将值存储在您自己的caching或静态成员中 – 这在集群和其他奇怪的情况下是不安全的,Application Server可能会对您的数据做出糟糕的事情。 而是使用有状态的bean或数据库。
  • 特别是, 永远不要在不同的远程调用中持有JDBC对象(Connections,ResultSets,PreparedStatements等) – 让Application Serverpipe理它。 Application Server不仅提供连接池,还cachingPreparedStatements。

消除泄漏

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

  1. 在开发过程中,尽早捕捉错误是最好的方法:

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

      1. 结对编程 ,教育那些没有足够经验的人
      2. 代码评论,因为许多眼睛比一个好
      3. unit testing ,这意味着你可以从一个testing工具中运行任何和所有的代码库,这使得重现泄漏琐碎
      4. 使用现有的库进行连接池,而不是自行构build
    2. 静态代码分析:使用像FindBugs这样的工具来执行静态代码分析。 这会挑选close()没有正确处理的地方。 Findbugs为Eclipse提供了一个插件,但它也可以独立运行,可以集成到Jenkins CI和其他构build工具中

  2. 在运行时:

    1. 可持续性和承诺

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

      1. 把好的日志语句放在你的代码中。 这些应该是清楚的,可以理解的,所以客户,支持人员和队友都可以不经过培训就能理解。 它们应该是简洁的,包括打印关键variables和属性的状态/内部值,以便跟踪处理逻辑。 良好的日志logging是debugging应用程序的基础,尤其是那些已部署的应用
      2. 您可以添加一个debuggingJDBC驱动程序到您的项目(用于debugging – 不实际部署它)。 一个例子(我没有使用它)是log4jdbc 。 然后你需要对这个文件做一些简单的分析,看看哪个执行没有相应的closures。 计算开放和closures应该突出显示是否有潜在的问题

        1. 监视数据库。 使用诸如SQL Developer的“Monitor SQL”函数或Quest的TOAD等工具监视正在运行的应用程序。 本文介绍了监视。 在监视过程中,您可以查询打开的游标(例如从表v $ sesstat)并查看它们的SQL。 如果游标的数量正在增加,并且(最重要的是)被一个相同的SQL语句所占据,那么您知道这个SQL有一个泄漏。 search你的代码和审查。

其他想法

你可以使用WeakReferences来处理closures连接吗?

弱和弱的引用是允许你引用一个对象的方式,允许JVM在它认为合适的时候(假设没有强对象引用链)垃圾收集对象。

如果将构造函数中的ReferenceQueue传递给软或弱引用,则当对象在GC中出现时(如果出现这种情况),该对象将放置在ReferenceQueue中。 通过这种方法,您可以与对象的最终化进行交互,您可以在那一刻closures或终止对象。

幻影引用有点怪异; 他们的目的只是为了控制finalization,但是你永远不能得到原始对象的引用,所以很难调用close()方法。

但是,尝试控制GC何时运行(Weak,Soft和PhantomReference会让您知道该对象已被GC队列入队 )通常不是一个好主意。 实际上,如果JVM中的内存量很大(例如-Xmx2000m),则可能永远不会 GC对象,并且您仍将体验ORA-01000。 如果JVM内存相对于程序要求较小,那么您可能会发现ResultSet和PreparedStatement对象在创build之后立即被GCed(在您读取之前),这可能会导致您的程序失败。

TL; DR:弱引用机制不是pipe理和closuresStatement和ResultSet对象的好方法。

我增加了更多的理解。

  1. 光标只是一个声明的客观; 它既不是resultSet也不是连接对象。
  2. 但是我们仍然必须closures结果集才能释放一些oracle内存。 如果你不closures不会被CURSORS计算的结果集。
  3. closures语句对象也会自动closures结果集对象。
  4. 将为所有的SELECT / INSERT / UPDATE / DELETE语句创build游标。
  5. 每个ORACLE数据库实例都可以使用oracle SID来标识; 同样,ORACLE DB可以使用连接SID来标识每个连接。 两个SID是不同的。
  6. 所以ORACLE会话不过是一个jdbc(tcp)连接; 这不过是一个SID。
  7. 如果我们将最大游标设置为500,那么它仅适用于一个JDBC会话/连接/ SID。
  8. 所以我们可以有很多的JDBC连接和它们各自的游标(语句)。
  9. 一旦JVM终止,所有的连接/游标将被closures,或者JDBCConnection被closures,CURSORS关于该连接将被closures。

Loggin as sysdba。

在Putty(Oraclelogin)中:

  [oracle@db01 ~]$ sqlplus / as sysdba 

在SqlPlus中:

用户名: sys as sysdba

将session_cached_cursors值设置为0,以便它不会有closures游标。

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

在数据库中select每个连接的现有OPEN_CURSORS valuse集

  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' 

现在debugging代码并享受! 🙂

纠正你的代码是这样的:

 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() } 

你确定,你真的closures你的pStatements,连接和结果吗?

为了分析开放对象,你可以实现一个委托模式,它将代码放在你的状态,连接和结果对象周围。 所以你会看到,如果一个对象将成功closures。

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

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

如果特定数据源的“语句高速caching大小”设置大约等于或大于Oracle数据库的最大打开游标数设置,则所有打开的游标都可以由WebLogic保留的cachingSQL语句使用,从而生成在ORA-01000错误。

为了解决这个问题,将针对每个指向Oracle数据库的WebLogic数据源的“语句高速caching大小”设置降低到远低于数据库上的最大光标数设置。

在WebLogic 10pipe理控制台中,可以在服务(左导航)>数据源>(单个数据源)>连接池选项卡上find每个数据源的语句高速caching大小设置。

你设置了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 

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

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

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

我们通过使用查询来检查运行我们的代码时打开的游标的数量 ,通过debugging器并select性地注释事件来发现这一点。

至于为什么每个新的引用打开另一个游标 – 所讨论的实体有其他实体的集合映射到它,我认为这是有关它(也许不只是这一个,而是与我们如何configuration获取模式和caching设置)。 Hibernate本身在closures打开的游标时会出现一些错误 ,虽然看起来这些在更高版本中已经修复。

因为无论如何我们并不需要对同一个实体有太多的重复引用,所以解决scheme是停止创build并保存所有这些冗余引用。 一旦我们做到了这个问题,当离开。

当您使用连接池时,主要发生此问题,因为当您closures连接时,连接回到连接池,并且与该连接关联的所有游标都不会closures,因为连接到数据库仍处于打开状态。 所以一种方法是减less池中连接的空闲连接时间,所以每当连接闲置连接10秒钟,连接到数据库就会closures,并创build新的连接到池中。

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

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

我今天面临同样的问题(ORA-01000)。 我在try {}中有一个for循环,在Oracle数据库中多次执行SELECT语句(每次更改一个参数),并在finally {}中,我的代码像往常一样closuresResultset,PreparedStatement和Connection 。 但是,只要我达到了一个特定数量的循环(1000),我得到了关于太多打开游标的Oracle错误。

根据上面的Andrew Alcock的post,我进行了一些修改,以便循环内部 ,每次获取数据和循环之前closures每个结果集和每个语句,然后再解决问题。

另外,在另一个Oracle数据库(ORA-01000)的另一个插入语句循环中出现完全相同的问题,这次是在300个语句之后。 同样,它也是以相同的方式解决的,所以无论是PreparedStatement还是ResultSet,或者两者都算作打开的游标,直到它们被closures。

我也遇到过这个问题

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

我正在使用Spring FrameworkSpring JDBC进行dao层。

我的应用程序用来泄漏游标,几分钟后,它曾经给我这个例外。

经过大量彻底的debugging和分析之后,我发现在我正在执行的查询中使用的中有一个表中存在Indexing,Primary Key和Unique Constraints的问题。

我的应用程序试图更新错误索引 。 因此,无论何时我的应用程序正在索引列上进行更新查询,数据库都试图根据更新后的值进行重新索引。 它正在泄漏游标

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

我面临同样的问题,因为我查询数据库超过1000次迭代。 我已经尝试,最后在我的代码。 但仍然是错误的。

为了解决这个问题,我只是login到Oracle数据库并运行查询:

ALTER SYSTEM SET open_cursors = 8000 SCOPE = BOTH;

这立即解决了我的问题。