出现这种情况的原因是因为,对于全局变量,每一个session会生成一个本地copy,如果程序重新编译的话,就会因程序里原变量找不到而丢弃该变量,继而导致这个错误。
也就是说在一个会话中调用程序包package时,会生成package中全局变量的副本,如果在另一个会话中对此package进行编译就会使前一个会话中的副本失效,故而产生错误。
要想避免这个错误,可以使程序捕获ORA-06508:的错误进行处理,也可以重新初始化会话
前几天在生产环境编译了一个ORACLE 包,导致了OA接口中抛出ORA-06508的异常。
经检查,是由于OA接口中调用的PACKAGE的依赖包中定义了全局变量,若在package中定义了全局变量,该包被编译过但是应用没有重启或者没有刷新连接池,则会导致 "错误ORA-06508: PL/SQL: 无法找到正在调用 的程序单元"。
1.若PACKAGE中定义了全局变量或者全局常量,则此类包被称为有状态的包。
2.若有状态的包编译了,则上层应用连接池会认为该包无效了。
3.解决方式有两种
a.)简单粗暴的,直接重启上层应用。
b.)尽量不要在包中定义全局变量或者全局常量,若必须定义,则将全局变量或者全局常量定义到伴生包中。
导言
在我们需要与数据库进行交互时,应尽可能地使用存储过程——无论我们使用哪个数据库。这是假设这个数据库提供了编写存储过程的工具,大多数主要的数据库都确实如此,例如Oracle、MySQL和SQL Server。而且无论你是使用Java、.NET或任何其它的编程语言或框架。
在Oracle 里,如果你想编写存储过程你当然应该使用PL/SQL包。在这篇文章里,假设你一般了解PL/SQL 和非常熟悉PL/SQL 包。这篇文章关注于一个令人讨厌的错误,这个错误使许多使用PL/SQL以及使用API(例如JDBC)从应用层调用它的开发人员很苦恼。这个错误就是“ORA-04068: existing state of packages has been discarded”。这个错误是当Oracle认为你的包状态出于某种原因是无效的时候抛出的。在这篇文章里,我们将讨论:
“ORA-04068”错误是什么和它为什么发生,它会影响什么,以及建议的解决方法
下面我们将从定义“ORA-04068”错误开始。
注意: 在这篇文章的示例里使用的是Oracle 9.2.0.3,不过相同的概念在Oracle 10g 中应该也是适用的。
“ORA-04068”错误是什么和它为什么发生?
如果我们使用Oracle的oerr程序看看ORA-04068的定义,我们会得到下面的信息:
$oerr ora 04068
04068, 00000, "existing state of packages%s%s%s has been discarded"
//*Cause: One of errors 4060 - 4067 when attempt to execute a stored procedure.//*Action: Try again after proper re-initialization of any//application's state.
这个错误显示执行包的现有状态被另一个会话的一个动作无效化了。这个“状态”涉及包在规范或体中定义的任何全局变量(包括常量)。引起这个错误的动作一般是(但不局限于此)在得到了发生错误的会话所使用的连接之后包的重新编译。Oracle 建议的动作是重新初始化应用程序状态以调整包的新状态后重新尝试。
如果我们看些例子就会明白得多。
假设有下面定义的表:
create table t (x number );
有个叫做pkg 的包具有一个叫做p的存储过程,如下所示:
create or replace package pkg as
procedurep;endpkg;/
下面所显示的包pkg的包体定义了存储过程p只是插入一个常量1到我们先前定义的表t中
create or replace package body pkg as
procedurepis
begin
insert into t(x) values (1);endp;endpkg;/
注意在包规范或包体中没有全局变量或常量。换句话说,这个包是“无状态的”。
我们将使用两个SQL*Plus 会话来解释这个概念。在每一个“体验”中,我们会在每一个会话中编译包体之后执行存储过程pkg.p。现在开始体验1,在体验1中,即使我们在另一个会话中编译包体也不会出现ORA-04068错误的。这是因为这个包是“无状态的”,它在规范或体中没有定义任何全局变量或常量。
体验1
假设表t和包pkg的规范以及包体已经在包里定义了。在SQL*Plus 会话1中,我们执行包并获得下面的结果(这个包执行成功)。
注意:你可能注意到在这篇文章里启动SQL*Plus 有时和常规启动("SQL >")不一样——例如,在下面代码的“session 1”中。例如这可以使用命令“set sqlprompt 'session 1”来实现。
session 1> execpkg.p
PL/SQL procedure successfully completed.
在SQL*Plus会话2中,我们通过像下面这样重新创建包来重新编译它:
session 2> create or replace package body pkg as
procedurepis
begin
insert into t(x) values (1);endp;endpkg;/Package body created.
session2>show errors;
No errors.
现在如果你回到会话1并重新执行包存储过程p,它会成功执行。
session 1> execpkg.p
PL/SQL procedure successfully completed.
让我们看看到目前为止我们所做的。我们定义了一个简单的包,只具有一个插入一个常量到一个数据表中的存储过程。我们开启了一个会话并执行这个包存储过程。在另一个会话中我们重新编译这个包(通过重新创建它)。当我们在第一个会话中重新执行这个包时,它运行正常——特别是,在会话2中包的重新编译在会话1中存储过程的第二次执行没有出现任何错误。
现在让我们重复这整个过程,只改变一个地方——添加一个全局变量到包体中(添加到规范中是一样的)。这意味着我们给包添加了“状态”。我们在下一节“体验2”中将讲述只做了这一个改变的相同体验。
体验2
我们从之前的会话退出。开启一个新的会话并在会话1中编辑我们新的包体,如下所示——注意在包的开始部分有一个常量声明,如下面的粗体显示。这是包的状态。这个常量不会被使用,但是它是这个体验没有得到结果的原因。
session1>@pkg_bodysession1>create or replace package body pkg asg_constant constantnumber := 1;procedurepis
begin
insert into t(x) values (1);endp;endpkg;/Package body created.
session1>show errors;
No errors.
现在我们在会话1中执行存储过程p。
session 1> execpkg.p
PL/SQL procedure successfully completed.
开启一个新的会话“session 2”并通过重新创建这个包来重新编译它。
session 2> @pkg_bodysession2> create or replace package body pkg asg_constant constantnumber := 1;procedurepis
begin
insert into t(x) values (1);endp;endpkg;/Package body created.
session2> show errors;
再次在会话1中执行存储过程p,得到下面的结果:
session1>execpkg.pBEGIN pkg.p; END;*ERROR at line1:
ORA-04068: existing state ofpackages has been discarded
ORA-04061: existing state ofpackage body "ORA92.PKG" has been invalidated
ORA-04065: not executed, altered ordropped package body "ORA92.PKG"
ORA-06508: PL/SQL: could notfind program unit being called
ORA-06512: at line 1
发生了什么?当我们重新在会话2中编译包体时,我们重置了包的状态。换句话说,对于任何在包编译之前连接的会话,包的执行状态(在这种情况下,由在包体里指定给常量的值来定义)被从内存中删除了。注意,我们实际上没有改变这个状态(我们在重新编译的时候保持相同的常量值),但是Oracle没有跟踪这一级别上的细节。只要连接了Oracle,在会话2里,重新编译了包pkg——这个包的状态现在重置为一个新的状态——所以对于任何在这个重新编译发生之前已经连接到Oracle的已有会话来说,包的状态变为无效。因此任何存储过程或包的函数下一次执行时,就会立即抛出ORA-04068错误。
如果我们在第一次尝试中得到了ORA-04068错误,那么我们在会话1中重新执行这个包会发生什么呢?让我们看一下。
session 1> execpkg.p
PL/SQL procedure successfully completed.
如同你所看到的,第二次执行显示调用应用程序(在这个例子中是SQL*Plus)调整为新的状态(自从Oracle通知了一次改变的状态)并重新执行有了新状态的包。这是Oracle 所建议的动作(查看这一节的开始部分):
在恰当地重新初始化任何应用程序状态之后重新尝试。
下面讲述ORA-04068错误的一些影响。
“ORA-04068”错误的影响
要测量ORA-04068的影响,你所要做的一切就是google它。它的两个主要影响如下:
大多数企业应用程序使用缓存连接的连接池。现在无论何时部署一个新的包规范,都需要在生产过程中重新编译。你编译的时候,对于连接池中的所有连接,这个包的状态都会被无效化,因为这个包在获得连接之后进行了重新编译(作为连接池初始化的一部分,有时更早)。注意,无论你是否改变这个状态,无论你是否改变代码,都会如此。当一个存储过程或一个函数第一次被调用时,它将会失败并抛出“ORA-04058”错误。所以一般情况下,你需要记住要“刷出”连接池(意味着丢弃现有连接并获得到Oracle的新连接)。这通常导致应用程序部署的一个停机。例如,如果你正在使用tomcat 和在tomcat 中的一个连接池,那么你可能需要停止tomcat 并重启——以便它重新初始化连接池。那么如果有一个长时间运行的批处理正在使用一个连接来执行与需要重新编译的包完全无关的一些逻辑呢?那么你或者需要等待这个批处理执行完毕或者在部署过程中将它关闭以便你可以重新初始化连接池。正如你可能想到的,这在应用程序有效性方面会是个梦魇。
很糟糕的一个影响是开发人员会困惑于为什么一个简单包(具有一个状态)的重新编译会导致在Oracle中得到这个错误。特别是在其它的数据库例如SQL Server和MySQL没有相同的包概念,因此没有与存储过程或函数相关联的一个状态。所以,在这些数据库中,你可以重新部署存储过程,并且应用程序会透明地使用它们。对于其它的数据库这是否是一个正确的选择是具有争议的,并且不属于本篇文章要讨论的范围。除了了解ORA-04068错误的根本原因以及怎样处理它之外,这个错误还会使得开发人员放弃去一起使用存储过程(并从而放弃了使用存储过程所带来的所有好处),并在他们的应用程序代码中嵌入SQL语句(例如在Java代码中嵌入SQL)。
那么解决方法是什么?
在这一节,我们将讨论处理“ORA-04068”错误的许多解决方法。每一个解决方法都在它的可用性方面具有一些局限性。这些解决方法还显示了思考的过程,使得更容易理解推荐的解决方法和替代方法。
让我们从解决方法1开始。
解决方法1:使用无状态包
最简单的解决方法是在你的系统中只使用无状态的包。正如我们在前面章句提到的,当你重新执行一个无状态的包时即使是它在另一个会话中重新编译之后也不会发生ORA-04068错误的。这是因为没有可以被Oracle无效化的状态。
这个解决方法,虽然在理论方面很简单,但是具有以下明显的缺陷:
它使你不能定义任何状态,这导致代码很差。一般有两种类型的状态:
一个全局变量:一般应该尽量避免全局变量。我遇到过的确需要在一个PL/SQL包或体中定义全局变量的合理需求。
一个全局常量:几乎所有的重要产品系统都需要定义常量。如果你决定在你的系统中不允许定义常量,那么就会导致很差的代码、多次重复定义相同的值,当需求改变的时候就会影响系统中不只一个地方,因此降低了可维护性。
如果你已经有一个包含了定义状态的包的系统,那么这个解决方法会使得进行大量的重写。在这种情况下,你需要决定是否值得这么做。
让我们看下一个解决方法:
解决方法2: 将所有的包状态移到另一个包里
这个解决方法的思想是将包体或包规范中的所有包状态移到另一个包里,这个包作为“同伴状态包”。这意味着我们降低了需要处理“ORA-06068”错误的次数,因为这些包本身并不存储任何状态,尽管它们因为各自的状态而依赖于同伴包。在我的经历中,在包体执行中发生的大多数改变——如果我们执行这个解决方法那就不会导致ORA-06068错误。如果我们重新编译同伴状态包,那仍然会发生ORA-06068错误。
让我们看看这个解决方法的工作情况。
我们创建一个新的包叫做const ,如下所示,我们将我们之前定义的常量移到我们的包pkg的包体中。
create or replace package const asg_constant constantnumber := 1;endconst;/show errors;
包pkg的包规范没有改变,并且为了你的方便,下面重复一下:
create or replace package pkg as
procedurep;endpkg;/show errors;
这个包体改变了,以便它之中不再有常量定义(它移到了包const中),而且现在插入语句使用包const 中定义的常量以获得这个值。因此包pkg依赖于包const以获得由常量g_constant定义的它的状态:
create or replace package body pkg as
procedurepis
begin
insert into t(x) values(const.g_constant);endp;endpkg;/show errors;
假设我们改变了对包pkg的包规范并在我们的系统中安装了一个新的包const。现在我们登录到我们的会话1中并执行这个存储过程——它如意料般地执行成功:
session 1>execpkg.p
PL/SQL procedure successfully completed.
我们登录到会话2中并重新编译包pkg的这个包规范和包体:
session 2>@pkg_specsession2>create or replace package pkg as
procedurep;endpkg;/Package created.
session2>show errors;
No errors.
session2>@pkg_bodysession2>create or replace package body pkg as
procedurepis
begin
insert into t(x) values(const.g_constant);endp;endpkg;/Package body created.
session2>show errors;
No errors.
在会话1中,当我们重新执行这个存储过程时,尽管我们重新编译了这个包规定和包体,它仍然执行成功。这是因为这个包状态是在包const中的(它已经被重新编译了),并因此当我们重新编译包pkg时包状态没有改变。.
当我们如下在会话2中重新编译包const时会发生什么呢?:
session 2>@constsession2>create or replace package const asg_constant constantnumber := 1;endconst;/Package created.
session2>show errors;
No errors.
如果我们在会话1中重新执行包pkg,我们将如意料般得到ORA-04068错误。这个错误清楚地表明在包const中的包状态改变了,并因此使得依赖于它的包pkg被无效化。
session 1>execpkg.pBEGIN pkg.p; END;*ERROR at line1:
ORA-04068: existing state ofpackages has been discarded
ORA-04061: existing state ofpackage "ORA92.CONST" has been invalidated
ORA-04065: not executed, altered ordropped package "ORA92.CONST"
ORA-06508: PL/SQL: could notfind program unit being called
ORA-06512: at "ORA92.PKG", line 5ORA-06512: at line 1
当然,如果我们之后在会话1中重新执行这个包,它看起来如预期般执行成功:
session 1>execpkg.p
PL/SQL procedure successfully completed.
解决方法2,尽管比解决方法1好些,但是具有以下缺陷:
它要求你总要将包的状态移到包外,因此使得包状态对于系统中的其它所有的包来说都是可见的。换句话说,你不能创建包私有的变量(或常量)(如果你在包体中声明一个变量或常量,它不能被其它的包访问到——它是定义它的包所私有的——这使得更好地封装代码)。这削弱了系统的封装性,从而降低了系统的可维护性。事实上,如果我们这么做,我们应该只将常量作为任何包状态的一部分(它是合理的并欢迎自我约束的)。
它要求你将包的所有状态移到一个同伴状态包里。这导致系统中同伴包的增大,所以这个解决方法不太好。如果你决定只有一个包具有所有其它包的状态,那么你将遇到另一个问题——在中央包里,一个变量或常量的改变会导致系统中所有其它包无效——甚至包括那些与这个变量或常量无关的包。只有你能决定这两个选择(中央状态包或每个包的同伴状态包)哪个适合于你。
如果你已有系统具有定义了状态的包,那么这个解决方法可能很难执行,因为它可能导致大量的重写。这种情况下你需要衡量是每一次部署出现连续的ORA-04068错误,还是要一次性重写系统。
我们下一组的解决方法是针对于前面提到的两个解决方法的改进,但它们有较大的缺陷,以至于在这篇文章里使得解决方法1或解决方法2是最终的推荐解决方法。但是,我强烈建议你看看下面两个解决方法来了解它们的机制,并基于你对系统的了解来作出判断。
解决方法3: 监测ORA-0408错误并重新执行包的存储过程
这个解决方法将处理错误的责任放到了客户端。它的思想是Oracle 通过生成错误ORA-04068给客户端提供了关于包状态已经被无效化的信息和由客户端来监测这个错误以及作出反应。客户端可以选择重新执行这个存储过程,如果它需要的话。我们已经看到这个解决方法看起来是工作在SQL*Plus 中,当存储过程的执行是在这个错误如意料般的发生之后。我们现在将看看在使用JDBC的Java程序中它的执行以及看看它是否管用。
首先让我们回到在包pkg中有状态的旧代码。所以我们在包体中重新引进状态,像以前一样——这个代码复制到下面以方便你查看:
create or replace package body pkg asg_constant constantnumber := 1;procedurepis
begin
insert into t(x) values (1);endp;endpkg;/show errors;
假设我们重新编译了包体以便我们具有恰当的新代码。我们将首先在一个Java程序中使用JDBC进行模拟一个会导致ORA-04068错误的环境。为此我们将:
使用JDBC在Java程序中获得一个连接,
在Java程序中使用JDBC执行pkg.p 存储过程,
在Java程序中休眠一段时间(10到20秒),
当我们的Java 程序休眠时,我们在一个单独的SQL*Plus会话中重新编译包pkg的包体,
在Java程序中使用JDBC重新执行pkg.p 存储过程——这将导致ORA-04068错误。
叫做ExecutePackageProcedureTwice 的Java程序显示如下。它执行了pkg.p存储过程,休眠了20秒以给我们充足的时间来重新编译这个包以模拟部署,然后重新执行这个存储过程:
packagedbj2ee.article2.design1;importjava.sql.CallableStatement;importjava.sql.Connection;importjava.sql.DriverManager;importoracle.jdbc.OracleDriver;public classExecutePackageProcedureTwice {public static void main(String[] args) throwsException {
Connection conn= null;
CallableStatement cstmt= null;long sleepInSecs = 20;try{
conn=getConnection();
cstmt= conn.prepareCall("{call pkg.p()}");
executePkg(conn, cstmt);
System.out.println("Sleeping for " + sleepInSecs + " seconds...");
Thread.sleep(sleepInSecs* 1000);
System.out.println("Out of sleep...");
executePkg(conn, cstmt);
}finally{try{if (cstmt != null)
cstmt.close();if (conn != null)
conn.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}private static Connection getConnection() throwsException {
DriverManager.registerDriver(newOracleDriver());return DriverManager.getConnection("jdbc:oracle:thin:@hercdev:1521:hercdev", "hercules", "hercules");
}private static void executePkg(Connection conn, CallableStatement cstmt) throwsException {
System.out.println("Executing the package...");
cstmt.executeUpdate();
conn.commit();
}
}
现在让我们再生ORA-04068错误。
设置恰当的CLASSPATH路径,指向类的根路径和classes12.jar(这个Jar包含Oracle JDBC执行),执行这个类,我们得到下面的结果:
M:\articles\dbj2ee\articles>java -cp "M:\classes12.jar;.;M:\learning\java\dbj2ee
\build\classes" dbj2ee.article2.design1.ExecutePackageProcedureTwice
Executing the package...
Sleeping for 20 seconds...
当Java程序运行到它开始休眠的地方时,我们在一个单独的SQL*Plus会话中重新编译这个包:
SQL>@pkg_body
SQL> create or replace packagebody pkg as
g_constant constant number := 1;
procedure p
is
begin
insert into t(x) values (1);
end p;
end pkg;/Package body created.
SQL>show errors;
No errors.
然后在Java程序结束休眠后,它如预期般地在试图第二次执行这个包的时候丢出ORA-04068错误:
M:\articles\dbj2ee\articles>java -cp "M:\classes12.jar;.;M:\learning\java\dbj2ee
\build\classes"dbj2ee.article2.design1.ExecutePackageProcedureTwice
Executing the package...
Sleepingfor 20seconds...
Out ofsleep...
Executing the package...
Exceptionin thread "main" java.sql.SQLException: ORA-04068: existing state of p
ackages has been discarded
ORA-04061: existing state of package body "ORA92.PKG"has been invalidated
ORA-04065: not executed, altered or dropped package body "ORA92.PKG"ORA-06508: PL/SQL: could not findprogram unit being called
ORA-06512: at line 1at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:134)
at oracle.jdbc.ttc7.TTIoer.processError(TTIoer.java:289)
at oracle.jdbc.ttc7.Oall7.receive(Oall7.java:573)
at oracle.jdbc.ttc7.TTC7Protocol.doOall7(TTC7Protocol.java:1891)
at oracle.jdbc.ttc7.TTC7Protocol.executeFetch(TTC7Protocol.java:955)
at oracle.jdbc.driver.OracleStatement.executeNonQuery(OracleStatement.ja
va:2053)
at oracle.jdbc.driver.OracleStatement.doExecuteOther(OracleStatement.jav
a:1940)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStateme
nt.java:2709)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePrepar
edStatement.java:589)
at dbj2ee.article2.design1.ExecutePackageProcedureTwice.executePkg(Execu
tePackageProcedureTwice.java:38)
at dbj2ee.article2.design1.ExecutePackageProcedureTwice.main(ExecutePack
ageProcedureTwice.java:20)
现在,正如我们所说的&#x