ORA-01000:超出最多允許打開的遊標數


 

oracle 使用 OPEN_CURSORS 參數指定一個會話一次最多可以打開的遊標的數量。超過此數量時,Oracle 將報告 orA-01000 錯誤。當此錯誤傳播到 WebLogic Server 時,就會拋出 SQLException。


java.sql.SQLException: orA-01000: maximum open cursors exceeded







診斷查詢
以下 SQL 查詢有助於診斷 orA-01000 問題。要執行這些查詢,需要以管理員身份登錄數據庫,或獲得數據庫管理員從那些 v$ 視圖中進行選擇的授權。


1. 檢查數據庫中的 OPEN_CURSORS 參數值。
oracle 使用 init.ora 中的初始化參數 OPEN_CURSORS 指定一個會話一次最多可以擁有的遊標數。缺省值爲 50。遺憾的是,此缺省值通常對 WebLogic Server 這樣的系統來說過小。要獲得數據庫中 OPEN_CURSORS 參數的值,可以使用以下查詢:
 
SQL> show parameter open_cursors;
NAME                                 TYPE        VALUE
------------------------------------ ----------- ---------------
open_cursors                         integer     1000

  

重要的是將 OPEN_CURSORS 的值設置得足夠大,以避免應用程序用盡所有打開的遊標。應用程序不同,該值也不同。即便會話打開的遊標數未達 OPEN_CURSORS 指定的數量(即設置的值高於實際需要的值),也不會增加系統開銷。


2. 獲取打開的遊標數。
下面的查詢按降序顯示用戶“SCOTT”爲每個會話打開的遊標數。

 
SQL> select o.sid, osuser, machine, count(*) num_curs
        from v$open_cursor o, v$session s
       where user_name = 'SCOTT' and o.sid=s.sid 
       group by o.sid, osuser, machine
      order by  num_curs desc;
 
  SID OSUSER       MACHINE           NUM_CURS
-----------------------------------------------------
       217           m1                1000
        96           m2                10
       411           m3                10
        50          test                9



在 WebLogic Server 中使用連接池時,此查詢中的 user_name 應爲用於創建連接池的 user_name(假定是從連接池得到連接)。該查詢結果還給出了計算機名稱。請在查詢結果中找出打開遊標數量大的 SID 和運行 WebLogic Server 的計算機的名稱。
請注意,v$open_cursor 可以跟蹤會話中 PARSED 和 NOT CLOSED 的動態遊標(使用 dbms_sql.open_cursor() 打開的遊標)。它不會跟蹤未經分析(但已打開)的動態遊標。在應用程序中使用動態遊標並不常見。本模式的前提是未使用動態遊標。

3. 獲取爲遊標執行的 SQL。
使用在以上查詢結果中找到的 SID 運行下面的查詢:


SQL> select q.sql_text
          from v$open_cursor o, v$sql q
          where q.hash_value=o.hash_value and o.sid = 217;
SQL_TEXT
------------------------------------------------------------------------------------------------------------------------
select * from empdemo where empid='212'
select * from empdemo where empid='321'
select * from empdemo where empid='947'
select * from empdemo where empid='527'
...

  



常見成因及解決辦法
下面是查找問題成因及可能的解決辦法的步驟。

代碼慣例
此問題的最常見成因是未正常關閉 JDBC 對象。使用診斷查詢中第三個查詢的結果在應用程序代碼中反向跟蹤,確保將所有 JDBC 對象都正常關閉。BEA 建議在 finally 塊中顯式關閉 Connection、Statement 和 ResultSet 等 JDBC 對象,以確保無論是在正常還是異常情況下都將所有 JDBC 對象關閉。下面是一個常規示例:


Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
    conn = getConnection(); //Method getConnection will return a JDBC Connection
    stmt = conn.createStatement();
    rs = stmt.executeQuery("select * from empdemo");
    // do work
} catch (Exception e) {
    // handle any exceptions
} finally {
    try {
        if(rs != null)
            rs.close();
    } catch (SQLException rse) {}
    try {
        if(stmt != null)
            stmt.close();
    } catch (SQLException sse) {}
    try {
        if(conn != null)
            conn.close();
    } catch (SQLException cse) {}
}

  

請避免採用任何放棄 JDBC 對象的代碼慣例。下面的代碼慣例在每個循環迭代中都獲得一個新的 Connection、Statement 和 ResultSet,但它沒有關閉每個迭代的 JDBC 對象。因此,它會導致 JDBC 對象泄漏。

Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
String[] queries = new String[10];
//Define queries
try {
    for(int i = 0; i < 10; i++) {
        conn = getConnection();
        stmt = conn.createStatement();
        rs = stmt.executeQuery(queries[i]);
        // do work
    }
} catch (Exception e) {
    // handle any exceptions
} finally {
    try {
        if(rs != null) 
            rs.close();
    } catch (SQLException rse) {}
    try {
        if(stmt != null) 
            stmt.close();
    } catch (SQLException sse) {}
    try {
        if(conn != null) 
            conn.close();
    } catch (SQLException cse) {}
}


儘管根據 JDBC 規範的規定,關閉 Connection 時正常情況下也會將 Statement 和 ResultSet 關閉,但好的做法是:如果在一個 Connection 對象上創建了多個 Statement,則在使用完 Statement 和 ResultSet 後立即顯式將它們關閉。如果未立即顯式關閉 Statement 和 ResultSet,遊標可能會積聚並在關閉 Connection 前超過數據庫允許的最大數量。例如,在以下代碼片斷中,正常情況下通過 finally 塊關閉 Connection 時,也會將 ResultSet 和 Statement 關閉。不過,此代碼片斷在一個連接上創建了多個 Statement 和 ResultSet。因此在循環完成前,可能已發生“超出最多允許打開的遊標數”問題。



Connection conn = null;
try{
    conn = getConnection();

    for(int i = 0; i < NUM_STMT; i++) {
        Statement stmt = null;
        ResultSet rs = null;
 
         stmt = conn.createStatement();
         rs = stmt.executeQuery(/*some query*/);
        //do work
    }
} catch(SQLException e) {
     // handle any exceptions
} finally {
    try{
        if(conn != null)
            conn.close();
    } catch(SQLException ignor) {}
}






語句緩存
爲提高性能,WebLogic Server 提供了一種功能,讓您可以在使用連接池時將預處理語句和可調用語句載入緩存。當 WebLogic Server 將預處理語句或可調用語句載入緩存時,在許多情況下,DBMS 將爲每個打開的語句都保留遊標。因此,語句緩存可能是“超出最多允許打開的遊標數”問題的成因。“語句緩存大小”屬性決定在每個連接池實例中爲每個連接緩存的預處理和可調用語句的總數。如果緩存的語句過多,可能會導致超過數據庫服務器上打開遊標數的上限。
請注意,各版本 WebLogic Server 的缺省語句緩存大小是有差異的。示例: 


在 WebLogic Server 6.1 中,缺省的預處理語句緩存大小爲 0 (http://e-docs.bea.com/wls/docs61/adminguide/jdbc.html#1133404 (English))。

在 WebLogic Server 7.0 中,非 XA 和 XA 預處理語句的缺省緩存大小爲 5/語句 (http://e-docs.bea.com/wls/docs70/adminguide/jdbc.html#1144702 (English))。

在 WebLogic Server 8.1 中,預處理語句和可調用語句的缺省緩存大小合計爲 10 (http://e-docs.bea.com/wls/docs81/ConsoleHelp/jdbc_connection_pools.html#1107805 (English))。

此外,在不同的 WebLogic Server 版本中,語句緩存的連接池屬性名稱和緩存算法的可配置性是不同的(有關詳細信息,請參閱文檔)。因此,如果最近對舊版本 WebLogic Server 進行了升級,語句緩存的行爲變化可能會影響打開遊標的數量。請將這種情況考慮在內。
要確定“超出最多允許打開的遊標數”問題是否與語句緩存有關,可以通過將語句緩存大小設置爲 0 將此功能關閉或減少緩存大小,再確認是否仍會出現錯誤。如果在減少緩存大小後問題沒有發生,則說明連接池原有的語句緩存過大或 DBMS 中打開遊標數的上限過低。可能需要考慮調整其中的一個值。如果發現連接上打開的遊標數持續增加,但在將語句緩存大小設置爲 0 後沒有出現這種現象,則可能說明存在遊標泄漏問題。這可能是由使用的 JDBC 驅動程序所致,也可能是 WebLogic Server 本身的一個錯誤。請嘗試使用其它 JDBC 驅動程序。如果使用其它 JDBC 驅動程序後仍發生同樣的問題,請將此問題報告給 BEA,這樣支持工程師可以對問題做進一步探查,以確定該問題是否爲 WebLogic Server 自身的一個錯誤。



數據庫驅動程序
“超出最多允許打開的遊標數”問題的另一個可能成因是 JDBC 驅動程序有問題。爲分清問題是驅動程序問題還是 WebLogic 連接池問題,如果有可重現的測試案例,可以嘗試執行以下步驟。

1. 直接從驅動程序獲取連接。
在測試案例中,繞過 WebLogic 連接池直接從驅動程序獲取 JDBC 連接。但請不要關閉連接,只需讓它們以數組或某種其它結構形式保持打開狀態,然後確認遊標泄漏是否仍然存在。不關閉連接是因爲要模擬使用連接池時的行爲。使用連接池時,connection.close() 並未真正地關閉物理連接,而是將連接返回到池中。

2. 嘗試使用其它 JDBC 驅動程序。
可以嘗試使用其它供應商的 JDBC 驅動程序或升級版的驅動程序,然後確認問題是否仍然存在。可以使用元數據來驗證所使用的驅動程序是否正確。示例代碼與下面的類似:

Connection conn = getConnection();
DatabaseMetaData dmd = conn.getMetaData();
System.out.println("JDBC Driver Name is " + dmd.getDriverName()); 
System.out.println("JDBC Driver Version is " + dmd.getDriverVersion());



3. XA 驅動程序錯誤。
如果使用的是 oracle XA 驅動程序,並且數據庫中出現了大量類似“Select count (*) FROM SYS.DBA_PENDING_TRANSACTIONS”的查詢,則可能是 oracle XA 驅動程序存在遊標泄漏問題。在有關 MetaLink 的案例 3151681 中有對此問題的描述,並且版本 10.1.0.2 中已修正了該問題。
此外,在使用 XA 驅動程序時,請確保按在 http://e-docs.bea.com/wls/docs81/jta/thirdpartytx.html#1075181 (English) 中所述,在 Database Server 上啓用 XA(例如,grant select on dba_pending_transactions to public)。

如果問題是 JDBC 驅動程序問題,但又不得不使用該驅動程序,一種以變通方式解決遊標泄漏問題的方法是不時重設 WebLogic 連接池,或收縮連接池。有關重設或收縮連接池的方法,請參閱 WebLogic 文檔(如果是 8.1 版本,該文檔位於 http://e-docs.bea.com/wls/docs81/ConsoleHelp/domain_jdbcconnectionpool_control.html (Enlish))。






已知問題
您可以定期查看所用 WLS 版本的“Release Notes”,瞭解 Service Pack 中的“Known Issues”或“Resolved Issues”的詳細信息及瀏覽與 orA-01000 /遊標泄漏有關的問題。方便起見,下面提供了這些發行說明的鏈接:
WLS 8.1 Release Notes (English)
WLS 7.0 Release Notes (English)
WLS 6.1 Release Notes (English)
對於需要特別注意之處,請參閱以下 CR,在相應版本 Service Pack 的發行說明中註明了已有針對它們的解決方法:
CR129379 - 如果 EJB 事務創建了許多新實體或以其它方式佔用了許多 Bean,而這些 Bean 均使用 JDBC,WebLogic Server 就存在 oracle 遊標數量不足的風險,因爲 WebLogic Server 在嘗試避免發生可疑的 oracle 驅動程序錯誤時會將對 JDBC 語句的關閉推遲到事務結束後進行,同時也將語句的遊標一直保留到那個時候。WLS 6.1 SP7、WLS 7.0 SP5 和 WLS 8.1 SP3 中對此行爲做了改動,會話已不必將遊標一直保留到事務結束。

CR179600 - WLS 8.1 SP3 - 發生某些語句故障時,連接池的語句緩存會遺漏關閉語句的操作,進而導致 DBMS 會話中發生遊標泄露。爲解決此問題,已在 WLS 8.1 SP4 中做了改動。
CR174126 - 在單個事務中使用多個不同的預處理語句引發了 XA 語句緩存錯誤,緩存因此丟失了未關閉 JDBC 語句的句柄。這迅速導致 oracle 中所有可用的遊標被消耗和泄漏。在 WLS 8.1 SP3 (English) 中對用於實現 XA 語句緩存的底層回收對象進行了修正。
CR188814 - WLS 8.1 SP2 - 使用 EJB 2.0 CMP 時,更新 BLOB 或 CLOB 列後未關閉 PreparedStatement 和 ResultSet。WebLogic Server 現在通過 WLS 8.1 SP4 實現了在更新 BLOB/CLOB 時關閉 PreparedStatement 和 ResultSet。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章