ORA-01000(最大打开游标错误)是Oracle数据库开发中极为常见的错误。在Java上下文中,当应用程序尝试打开比数据库实例上配置的游标更多的ResultSet时,就会发生这种情况。
常见原因有:
配置错误
- 在应用程序中,查询数据库的线程比数据库中的游标的线程更多。一种情况是您的连接和线程池大于数据库上的游标数。
- 您有许多开发人员或应用程序连接到同一个数据库实例(可能包含许多架构),并且一起使用的连接过多。
解:
- 增加数据库上的游标数(如果资源允许)或
- 减少应用程序中的线程数。
游标泄漏
- 应用程序未关闭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泄漏的过程和工具:
在开发过程中-尽早发现错误是迄今为止最好的方法:
开发实践:良好的开发实践应在软件离开开发人员之前减少软件中的错误数量。具体做法包括:
- 配对编程,以教育没有足够经验的人
- 代码审查,因为许多眼睛胜过一只眼睛
- 单元测试,这意味着您可以使用测试工具来练习所有代码库,从而使重现泄漏变得微不足道
- 使用现有的库进行连接池,而不是构建自己的库
静态代码分析:使用出色的Findbugs之类的工具来执行静态代码分析。这会拾取许多未正确处理close()的地方。Findbugs有一个Eclipse插件,但也可以一次性运行,并集成到Jenkins CI和其他构建工具中
在运行时:
可保持性和提交
- 如果ResultSet的可保存性为ResultSet.CLOSE_CURSORS_OVER_COMMIT,则在调用Connection.commit()方法时关闭ResultSet。可以使用Connection.setHoldability()或使用重载的Connection.createStatement()方法进行设置。
在运行时记录。
- 在您的代码中放置良好的日志语句。这些内容应清晰易懂,以便客户,支持人员和队友无需培训即可理解。它们应该简洁,并包括打印关键变量和属性的状态/内部值,以便您可以跟踪处理逻辑。良好的日志记录是调试应用程序(尤其是已部署的应用程序)的基础。
您可以在项目中添加调试JDBC驱动程序(用于调试-请勿实际部署)。一个示例(我还没有使用过)是log4jdbc。然后,您需要对此文件进行一些简单的分析,以查看哪些执行没有相应的关闭。计算打开和关闭应该突出显示是否存在潜在问题
- 监视数据库。使用诸如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对象的好方法。
for (String language : additionalLangs) {