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
}