C#操作Oracle数据库连接超时的错误处理
创建时间: 2007/08/09
最近在使用C#操作Oracle数据库时发现了一个奇怪的问题, 在数据库会话存在超时限制时, 即使客户端重新连接数据库也无法继续数据库操作, 而且在连接时没有错误发生, 仅仅是 在连接后的操作中引发异常.
程序本身很简单, 从一个消息中间件(MOM)中接收消息数据, 然后保存到数据库. 由于该应用是一个后台服务, 需要对错误处理比较小心, 因为与存在用户交互(界面)的程序相比, 后台服务需要更加健壮和更好的容错能力.
因此在操作数据库时, 如果出现数据库连接问题而无法保存消息的情况, 应当不丢失消息并重新尝试连接数据库, 然后再次对消息进行处理.
对于其它类型的错误, 例如主/外键问题, 数据类型问题等等因为消息数据本身错误引发的异常, 则应当保存到文件中, 并记录日志, 供人工审核, 然后对下面的消息数据进行进行处理.
因此程序代码中对ORA-00028(session kill) ORA-02396(exceed idle time) ORA-01012(not logon)和 ORA-12535(timeout)等错误进行单独处理, 退出消息处理函数, 并在主函数中重新调用, 以重新连接数据库等资源.
在服务主函数中:
while (true)
{ try
{
ProcessMessage(queueName);
}
catch (Exception e)
{
LogManager.WriteLog(201, e.Message);
GC.Collect();
}
//Sleep
if (mre.WaitOne(cf.TIME_OUT, false))
break;
}
而在消息处理函数中:
private static void ProcessMessage(String queueName)
{
//Open database
String strConn = cf.DB_CONN_STR;
OracleConnection oc = new OracleConnection(strConn);
oc.Open();
//连接MOM....(略)
do
{
//读取MOM消息数据...(略)
try
{
//保存MOM消息数据到数据库的操作...(略)
str = msgInfo.MsgName + " save to database success.";
LogManager.WriteLog(301, str);
}
catch (OracleException e)
{
//In case of: ORA-00028(session kill)
// ORA-02396(exceed idle time)
// ORA-01012(not logon)
// ORA-12535(timeout)
if (e.Code == 2396 || e.Code == 1012 || e.Code == 28 || e.Code == 12535)
{
str = msgInfo.MsgName + " save to database fail, error code: " + e.Code.ToString() +", send message back and re-connect oracle.";
LogManager.WriteLog(301, str);
break; //quit loops
}
else //For other reason, just save data to dump file
{
//保存MOM消息数据到文件...(略)
}
}
catch (Exception e)
{
//保存MOM消息数据到文件...(略)
}
}
} while (true);
oc.Close();
}
然而在测试中, 发现对ORA-00028(session kill) ORA-01012(not logon)和 ORA-12535(timeout)均可成功的进行自动连接, 从而进行进行消息处理, 而对ORA-02396(exceed idle time) 的情况, 连接没有发现错误, 但是保存操作失败, 最终导致剩余的消息在数据库正常的情况下也无法进行处理了.
对于存在资源限制的数据库, 均可出现上述问题, 因为后台服务一般是常连接.
CREATE PROFILE developer_prof LIMIT
IDLE_TIME 60
CONNECT_TIME 480;
ALTER SYSTEM SET RESOURCE_LIMIT=TRUE;
在排除程序逻辑错误和数据库问题后, 唯一的疑点就是连接池, 也就是数据库重新连接时, 从客户端连接池中取出了一个现存的连接, 以提供效率, 但是正是这个连接已经 超时, 因此连接成功后, 操作还是引发了超时错误. 因此必须清除该连接池, 然后重新连接. 对.NET FRAMEWORK2.0版中OracleConnection新增了静态函数ClearPool和ClearAllPool.
ClearPool方法在连接池中清除特定连接(通过参数指定)。如果在调用时该连接正在使用,则会对它们进行适当地标记,当该连接Close时,连接将被丢弃而不保存到连接池中。
ClearAllPools则在连接池中清除所有连接。如果在调用该方法时连接正在使用,则会对连接进行适当地标记,当对连接调用 Close 时,连接将被丢弃而不保存到连接池。
因此解决方法很简单, 在适当的地方添加连接池清除操作, 可以选择对所有异常出现的情况, 也可以针对连接超时等需要清空连接池的情况.例如:
if (e.Code == 2396)
{
//Clear connection pool and close connection
OracleConnection.ClearPool(oc);
str = msgInfo.MsgName + " save to database fail, error code: " + e.Code.ToString() +", send message back and re-connect oracle.";
LogManager.WriteLog(301, str);
break; //quit loops
}