java.sql.SQLException: – ORA-01000:超过最大打开游标
我得到一个ORA-01000 SQLexception。 所以我有一些相关的疑问。
- 最大打开的游标是否与JDBC连接的数量完全相关,还是与我们为单个连接创build的语句和结果集对象有关? (我们正在使用连接池)
- 有没有办法configuration数据库中的语句/结果集对象的数量(如连接)?
- 在单线程环境中使用实例variables语句/结果集对象而不是方法本地语句/结果集对象是否明智?
-
在循环中执行预处理语句是否会导致此问题? (当然,我可以使用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
-
如果在单个连接对象上多次调用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了游标的情况。
常见的原因是:
-
configuration错误
- 您的应用程序查询数据库中的线程数多于数据库上的游标。 一种情况是您的连接和线程池的数量大于数据库上的游标数量。
- 您有许多开发人员或应用程序连接到相同的数据库实例(可能包含许多模式),并且您使用的连接太多。
-
解:
- 增加数据库上游标的数量 (如果资源允许)或
- 减less应用程序中的线程数量。
-
游标泄漏
- 应用程序不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泄漏:
-
在开发过程中,尽早捕捉错误是最好的方法:
-
开发实践:良好的开发实践应该在软件离开开发人员的桌面之前减less软件中的错误数量。 具体做法包括:
- 结对编程 ,教育那些没有足够经验的人
- 代码评论,因为许多眼睛比一个好
- unit testing ,这意味着你可以从一个testing工具中运行任何和所有的代码库,这使得重现泄漏琐碎
- 使用现有的库进行连接池,而不是自行构build
-
静态代码分析:使用像FindBugs这样的工具来执行静态代码分析。 这会挑选close()没有正确处理的地方。 Findbugs为Eclipse提供了一个插件,但它也可以独立运行,可以集成到Jenkins CI和其他构build工具中
-
-
在运行时:
-
可持续性和承诺
- 如果ResultSet可保存性为ResultSet.CLOSE_CURSORS_OVER_COMMIT,则在调用Connection.commit()方法时,ResultSet将closures。 这可以使用Connection.setHoldability()或使用重载的Connection.createStatement()方法进行设置。
-
在运行时logging。
- 把好的日志语句放在你的代码中。 这些应该是清楚的,可以理解的,所以客户,支持人员和队友都可以不经过培训就能理解。 它们应该是简洁的,包括打印关键variables和属性的状态/内部值,以便跟踪处理逻辑。 良好的日志logging是debugging应用程序的基础,尤其是那些已部署的应用
-
您可以添加一个debuggingJDBC驱动程序到您的项目(用于debugging – 不实际部署它)。 一个例子(我没有使用它)是log4jdbc 。 然后你需要对这个文件做一些简单的分析,看看哪个执行没有相应的closures。 计算开放和closures应该突出显示是否有潜在的问题
- 监视数据库。 使用诸如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对象的好方法。
我增加了更多的理解。
- 光标只是一个声明的客观; 它既不是resultSet也不是连接对象。
- 但是我们仍然必须closures结果集才能释放一些oracle内存。 如果你不closures不会被CURSORS计算的结果集。
- closures语句对象也会自动closures结果集对象。
- 将为所有的SELECT / INSERT / UPDATE / DELETE语句创build游标。
- 每个ORACLE数据库实例都可以使用oracle SID来标识; 同样,ORACLE DB可以使用连接SID来标识每个连接。 两个SID是不同的。
- 所以ORACLE会话不过是一个jdbc(tcp)连接; 这不过是一个SID。
- 如果我们将最大游标设置为500,那么它仅适用于一个JDBC会话/连接/ SID。
- 所以我们可以有很多的JDBC连接和它们各自的游标(语句)。
- 一旦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 Framework和Spring JDBC进行dao层。
我的应用程序用来泄漏游标,几分钟后,它曾经给我这个例外。
经过大量彻底的debugging和分析之后,我发现在我正在执行的查询中使用的表中有一个表中存在Indexing,Primary Key和Unique Constraints的问题。
我的应用程序试图更新错误索引的列 。 因此,无论何时我的应用程序正在索引列上进行更新查询,数据库都试图根据更新后的值进行重新索引。 它正在泄漏游标 。
我能够通过对用于在查询中search的列进行适当索引并且在需要的地方应用适当的约束来解决问题。
我面临同样的问题,因为我查询数据库超过1000次迭代。 我已经尝试,最后在我的代码。 但仍然是错误的。
为了解决这个问题,我只是login到Oracle数据库并运行查询:
ALTER SYSTEM SET open_cursors = 8000 SCOPE = BOTH;
这立即解决了我的问题。