1. 程式人生 > >buffer cache 和shared pool詳解(之五,問題診斷總結)

buffer cache 和shared pool詳解(之五,問題診斷總結)

【深入解析--eygle】 學習筆記

1.2.7 診斷和解決ORA-04031 錯誤

Shared  Pool的主要問題在根本上只有一個,就是碎片過多帶來的效能影響

1.2.7.1 什麼是ORA-04031錯誤

當嘗試在共享池分配大塊的連續記憶體失敗(很多時候是由於碎片過多,而並非真是記憶體不足)時,Oracle首先清除共享池中當前沒使用的所有物件,使空閒記憶體塊合併。如果仍然沒有足夠大的單塊記憶體可以滿足需要,就會產生ORA-04031錯誤。

如下一段虛擬碼來描述04031錯誤的產生:

Scan free lists      --掃描Free Lists

if (request size of RESERVED Pool size)    --

如果請求RESERVED POOL空間

scan reserved list          --掃描保留列表

if (chunk found)           --如果發現滿足條件的記憶體塊

check chunk size and perhaps truncate  --檢查大小,可能需要分割

return               --返回

do LRU operation for n objects       --如果並非請求RESERVED POOL或不能發現足夠記憶體

scan free lists          --則轉而執行LRU操作,釋放記憶體,重新掃描

if  (request  sizes exceeds  reserved  pool min  alloc)  – 如果請求大於

_shared_pool_reserved_min_alloc

scan reserved list            --掃描保留列表

if (chunk found)              --如果發現滿足條件的記憶體塊

check chunk size and perhaps truncate    --檢查大小,可能需要分割

return               --在Freelist或reservedlist找到則成功返回

signal ORA-4031 error              --

否則報告ORA-04031錯誤。

[[email protected] ~]$ oerr ora 4031

04031, 00000, "unable to allocate %s bytes ofshared memory(\"%s\",\"%s\",\"%s\",\"%s\")"

// *Cause: More shared memory is needed than was allocated in the shared

//         pool or Streams pool.

// *Action: If the shared pool is out of memory,either use the

//         DBMS_SHARED_POOL package to pin large packages,

//         reduce your use of shared memory, or increase the amount of

//         available shared memory by increasing the value of the

//         initialization parameters SHARED_POOL_RESERVED_SIZE and

//         SHARED_POOL_SIZE.

//         If the large pool is out of memory, increase the initialization

//         parameter LARGE_POOL_SIZE. 

//         If the error is issued from an Oracle Streams or XStream process,

//         increase the initialization parameter STREAMS_POOL_SIZE or increase

//         the capture or apply parameter MAX_SGA_SIZE.

[[email protected] ~]$

1.2.7.2 繫結變數和cursor_sharing

如果SHARED_POOL_SIZE設定得足夠大,又可以排除Bug的因素,那麼大多數的ORA-04031錯誤都是由共享池中的大量的SQL程式碼等導致過多記憶體碎片引起的

可能的主要原因有:

(1)SQL沒有足夠的共享;

(2)大量不必要的解析呼叫;

(3)沒有使用繫結變數。

實際上說,應用的編寫和調整始終是最重要的內容,Shared  Pool的調整根本上要從應用入手。根本上,使用繫結變數可以充分降低Shared Pool和Library Cache的Latch競爭,從而提高效能。

反覆的SQL硬解析不僅會消耗大量的CPU資源,也會佔用更多的記憶體,嚴重影響資料庫的效能,而使用繫結變數則可以使SQL充分共享,實現SQL的軟解析,提高系統性能。

(1)建立表病記錄解析統計記錄:

Table created.

15:47:48 [email protected] SQL>SELECT NAME,VALUE FROMV$MYSTAT A,V$STATNAME B WHERE A.STATISTIC#=B.STATISTIC# AND NAME LIKE 'parse%';

NAME                                           VALUE

----------------------------------------------------

parse time cpu                                     28

parse time elapsed                                 82

parse count (total)                               294

parse count (hard)                                180

parse count (failures)                              0

parse count (describe)                              0

6 rows selected.

15:54:32 [email protected] SQL>

(2)進行迴圈插入資料,以下程式碼並未使用繫結變數:

felix SQL> begin

for i in 1..10 loop

execute immediate 'insert into felixvalues('||i||')';

end loop;

commit;

end;

/

PL/SQL procedure successfully completed.

(3)完成之後檢查統計資訊,注意硬解析次數增加了10次,也就是說每次INSERT操作都需要進行一次獨立的解析:

16:02:22 [email protected] SQL>SELECT NAME,VALUE FROMV$MYSTAT A,V$STATNAME B WHERE A.STATISTIC#=B.STATISTIC# AND NAME LIKE 'parse%';

NAME                                           VALUE

----------------------------------------------------

parse time cpu                                     32

parse time elapsed                                 89

parse count (total)                               336

parse count (hard)                                190

parse count (failures)                              1

parse count (describe)                              0

6 rows selected.

16:02:29 [email protected] SQL>

查詢V$SQLAREA檢視,可以找到這些不能共享的SQL,注 意 每 條SQL都只執行了一次,這些SQL不僅解析要消耗密集的SQL資源,也要佔用共享記憶體儲存這些不同的SQL程式碼:

  FROM v$sqlarea

  WHERE sql_text LIKE 'insert into felix%';

-------------------------------- ------------------------ ----------

insert into felix values(9)         1              1               1

insert into felix values(5)         1             1               1

insert into felix values(8)         1              1               1

insert into felix values(1)         1              1               1

insert into felix values(4)         1              1               1

insert into felix values(6)         1              1               1

insert into felix values(3)         1              1               1

insert into felix values(7)         1              1               1

insert into felix values(2)         1              1               1

insert into felix values(10)        1              1               1

10 rows selected.

重構測試表,進行第二次測試:

[email protected] SQL>drop  table felix purge;

[email protected] SQL>create table felix (id number);

begin

 for i in1..10 loop

 executeimmediate 'insert into felix values(:v1)' using i;

 end loop;

 commit;

 end;

 /

對於該SQL,在共享池中只存在一份,解析一次,執行10次,這就是繫結變數的優勢所在:

SELECT sql_text, version_count, parse_calls,executions

  FROMv$sqlarea

  WHEREsql_text LIKE 'insert into felix%';

SQL_TEXT                          VERSION_COUNT PARSE_CALLSEXECUTIONS

-------------------------------- ------------------------ ----------

insert into felix values(:v1)         1              1               1

在應用程式開發的過程中,都應該優先考慮使用繫結變數(在JAVA應用中可以使用PreparedStatement進行變數繫結),但是如果應用沒有很好地使用繫結變數,那麼Oracle從8.1.6開始提供了一個新的初始化引數用以在Server 端進行強制變數繫結,這個引數就是cursor_sharing。最初這個引數有兩個可選設定:exact和force。

預設值是exact,表示精確匹配;force表示在Server端執行強制繫結。在8i的版本里使用這個引數對某些應用可以帶來極大的效能提高,但是同時也存在一些副作用,比如優化器無法生成精確的執行計劃,SQL執行計劃發生改變等(所以如果啟用cursor_sharing引數時,一定確認使用者的應用在此模式下經過充分的測試)。

從Oracle 9i開始,Oracle引入了繫結變數Peeking的機制,SQL在第一次執行時,首先在Session的PGA中使用具體值生成精確的執行計劃,以期可以提高執行計劃的準確性,然而Peeking的方式只在第一次硬解析時生效,所以仍然可能存在問題,導致後續的SQL錯誤的執行;同時,在Oracle  9i中,cursor_sharing引數有了第3個選項:similar。該引數指定Oracle在存在柱狀圖資訊時,對於不同的變數值,重新解析,從而可以利用柱狀圖更為精確地制定SQL執行計劃。也即當存在柱狀圖資訊時,similar的表現和exact相同;當柱狀圖資訊不存在時,similar的表現和force相同。

除了Bug之外,在正常情況下,由於Similar的判斷機制,可能也會導致SQL無法共享。在收集了柱狀圖(Hisogram)資訊之後,如果SQL未使用繫結變數,當SQL使用具備柱狀圖資訊的Column時,資料庫會認為SQL傳遞過來的每個常量都是不可靠的,需要為每個SQL生成一個Cursor,這種情況被稱為UNSAFE  BINDS。大量的Version_Count可能會導致資料庫產生大量的cursor: pin S wait on X等待。解決這類問題,可以設定CURSOR_SHARING為Force或者刪除相應欄位上的柱狀圖資訊。

1.2.7.3 使用Flush Shared Pool緩解共享池問題

一種應急處理方法,強制重新整理共享池。

alter system flushshared_pool;

重新整理共享池可以幫助合併碎片(smallchunks), 強 制 老 化SQL,釋放共享池,但是這通常是不推薦的做法,這是因為:

(1)Flush Shared Pool會導致當前未使用的cursor被清除出共享池,如果這些SQL隨後需要執行,那麼資料庫將經歷大量的硬解析,系統將會經歷嚴重的CPU爭用,資料庫將會產生激烈的Latch競爭。

(2)如果應用沒有使用繫結變數,大量類似SQL不停執行,那麼Flush Shared Pool可能只能帶來短暫的改善,資料庫很快就會回到原來的狀態。

(3)如果Shared Pool很大,並且系統非常繁忙,重新整理Shared Pool可能會導致系統掛起,對於類似系統儘量在系統空閒時進行

1.2.7.4 SHARED_POOL_RESERVED_SIZE引數的設定及作用

shared_pool_reserved_size,該引數指定了保留的共享池空間,用於滿足將來的大的連續的共享池空間請求。當共享池出現過多碎片,請求大塊空間會導致Oracle 大範圍的查詢並釋放共享池記憶體來滿足請求,由此可能會帶來較為嚴重的效能下降,設定合適的shared_pool_reserved_size引數,結合shared_pool_reserved_min_alloc引數可以用來避免由此導致的效能下降。

這個引數理想值應該大到足以滿足任何對RESERVED  LIST的記憶體請求,而無需資料庫從共享池中重新整理物件。這個引數的預設值是shared_pool_size 的5%,通常這個引數的建議值為shared_pool_size引數的10%~20%大小,最大不得超過shared_pool_size的50%。

shared_pool_reserved_min_alloc這個引數的值控制保留記憶體的使用和分配。如果一個足夠尺寸的大塊記憶體請求在共享池空閒列表中沒能找到,記憶體就從保留列表(RESERVED  LIST)中分配一塊比這個值大的空間。

如果你的系統經常出現的ORA-04031錯誤都是請求大於4400的記憶體塊,那麼就可能需要增加shared_pool_reserved_size引數設定。

而如果主要的引發LRU合併、老化並出現04031錯誤的記憶體請求在4100~4400byte之間,那麼降低_shared_pool_reserved_min_alloc 同時適當增大SHARED_POOL_RESERVED_SIZE引數值通常會有所幫助。設定_shared_pool_reserved_min_alloc=4100可以增加Shared Pool成功滿足請求的概率。需要注意的是,這個引數的修改應當結合Shared  Pool Size和Shared Pool Reserved Size的修改。設定_shared_pool_reserved_min_alloc=4100是經過證明的可靠方式,不建議設定更低。

查詢v$shared_pool_reserved檢視可以用於判斷共享池問題的引發原因:

16:26:38 [email protected] SQL>S SELECT free_space,

      avg_free_size,

      used_space,

      avg_used_size,

      request_failures,

      last_failure_size

  FROMv$shared_pool_reserved;

FREE_SPACE AVG_FREE_SIZE USED_SPACE AVG_USED_SIZEREQUEST_FAILURES LAST_FAILURE_SIZE

---------- ------------- ---------- ----------------------------- -----------------

  7255512    196094.919    8155392        220416                0                 0

17:04:04 [email protected] SQL>

如果request_failures>0 並且last_failure_size>shared_pool_reserved_min_alloc,那麼ORA-04031 錯誤就可能是因為共享池保留空間缺少連續空間所致。要解決這個問題,可以考慮加大shared_pool_reserved_min_alloc 來降低緩衝進共享池保留空間的物件數目,並增大shared_pool_reserved_size和shared_pool_size來加大共享池保留空間的可用記憶體。

如果request_failures>0 並且last_failure_size<shared_pool_reserved_min_alloc 或者request_failures=0並且last_failure_size<shared_pool_reserved_min_alloc,那麼是因為在庫高速緩衝缺少連續空間導致ORA-04031錯誤。對於這一類情況應該考慮降低shared_pool_reserved_min_alloc以放入更多的物件到共享池保留空間中並且加大shared_pool_size。

1.2.7.5 其他

此外,某些特定的SQL,較大的指標或者大的Package都可能導致ORA-04031錯誤。在很多ERP軟體中,這樣的情況非常常見。在這種情況下,可以考慮把這個大的物件Pin 到共享池中,減少其動態請求、分配所帶來的負擔

使用dbms_shared_pool.keep 系統包可以把這些物件pin 到記憶體中,最常見的SYS.STANDARD、SYS.DBMS_STANDARD等都是常見的候選物件。

注意:要使用DBMS_SHARED_POOL系統包,首先需要執行dbmspool.sql指令碼,該指令碼會自動呼叫prvtpool.plb 指令碼建立所需物件。

引發ORA-04031 錯誤的因素還有很多,通過設定相關引數如session_cached_cursors、cursor_space_for_time等也可以解決一些效能問題並帶來針對性的效能改善,這裡不再過多討論。

1.2.8 Library Cache Pin 及Library Cache Lock分析

Oracle使用兩種資料結構來進行Library Cache的併發訪問控制:lock 和 pin

Lock可以被認為是解析鎖,而Pin則可以被認為是以讀取或改變物件內容為目的所加的短時鎖。之所以將Library  Cache Object物件分開,使用多個鎖定來保護,其中的一個重要目的就是為了提高併發。

Lock比Pin具有更高的級別。Lock在物件handle上獲得,在pin一個物件之前,必須首先獲得該handle的鎖定。Handle可以理解為Libray  Cache物件的Buffer  Header,其中包含了庫快取物件的名稱、標記、指向具體物件的記憶體地址指標等資訊。

再次引用一下前文曾經提到的圖表,通過下圖我們可以清晰的看到Object Handles和Heaps的關係:

 

鎖定主要有三種模式:  Null,share,Exclusive。在讀取訪問物件時,通常需要獲取Null(空)模式以及share(共享)模式的鎖定。在修改物件時,需要獲得Exclusive(排他)鎖定Library Cache Lock根本作用就是控制多個Oracle客戶端對同一個Library  Cache物件的併發訪問,通過對Library Cache Object Hadle上加鎖來防止非相容的訪問。

常見的使用或保護包括:

1.  一個客戶端防止其他客戶端訪問同一物件

2.  一個客戶端可以通過鎖定維持相對長時間的依賴性(例如,防止其他客戶端修改物件)

3.  當在Library Cache中定位物件時也需要獲得這個鎖定

在鎖定了Library Cache物件以後,一個程序在訪問之前必須pin該物件。同樣pin有三種模式,Null,shared和exclusive。只讀模式時獲得共享pin,修改模式獲得排他pin。通常我們訪問、執行過程、Package時獲得的都是共享pin,如果排他pin被持有,那麼資料庫此時就要產生等待。

為了實現更好的效能,從Oracle10gR2開始,Library Cache Pin已經逐漸被互斥機制(Mutex)所取代,在Oracle Database 11g中,這個變化就更為明顯。

1.2.8.1 LIBRARY CACHE PIN等待事件

library cache pin是用來管理library cache的併發訪問的,pin一個Object會引起相應的heap被載入記憶體中(如果此前沒有被載入),pins可以在Null、Share、Exclusive這3個模式下獲得,可以認為pin是一種特定形式的鎖。

當library cache pin等待事件出現時,通常說明該pin被其他使用者已非相容模式持有。library cache  pin的等待時間為3秒鐘,其中有1秒鐘用於PMON後臺程序,即在取得pin之前最多等待3秒鐘,否則就超時。

ibrary cache pin的引數有P1(KGL Handle Address)、P2(Pin Address)和P3(Encoded Mode & Namespace), 常用的主要是P1和P2

library cache pin通常是發生在編譯或重新編譯PL/SQL、VIEW、TYPES等Object時。

當Object變得無效時,Oracle會在第一次訪問此Object時試圖去重新編譯它,如果此時其他session已經把此Object  pin到library  cache 中,就會出現問題,特別時當有大量的活動session並且存在較複雜的dependence時。在某種情況下,重新編譯Object可能會花幾個小時時間,從而阻塞其他試圖去訪問此Object的程序。

recompile過程包含以下步驟:

(1)儲存過程的library  cache  object以排他模式被鎖定,這個鎖定是在handle上獲得的。Exclusive 鎖定可以防止其他使用者執行同樣的操作,同時防止其他使用者建立新的引用此過程的物件。

(2)以Shared模式pin該物件,以執行安全和錯誤檢查。

(3)共享pin被釋放,重新以排他模式pin該物件,執行重編譯。

(4)使所有依賴該過程的物件失效。

(5)釋放Exclusive Lock和Exclusive Pin。

從Oracle 10g開始,以上測試將不會看到同樣的效果,這是因為Oracle 10g對於物件編譯與重建做出了增強。注意當重新replace一個過程時,Oracle會首先執行檢查,如果程式碼前後完全相同,則replace工作並不會真正進行(因為沒有變化),物件的LAST_DDL_TIME不會改變,這就意味著Latch的競爭可以減少。

對於version_count過高的問題,可以查詢V$SQL_SHARED_CURSOR檢視,這個檢視會給出SQL不能共享的具體原因,如果是正常因素導致的,相應的欄位會被標記為“Y”;對於異常的情況(如本案例),查詢結果可能顯示的都是“N”,這就表明Oracle認為這種行為是正常的,在當前系統設定下,這些SQL不應該被共享,那麼可以判斷是某個引數設定引起的。和SQL共享關係最大的一個初始化引數就是cursor_sharing,在這個案例中cursor_sharing引數被設定為similar,正是這個設定導致了大量子指標不能共享。

1.2.9  V$SQL與V$SQLAREA檢視

在前面提到過一個經常被問及的問題:V$SQL與V$SQLAREA兩個檢視有什麼不同?所以有這樣一個問題是因為這兩個檢視在結構上非常相似。

V$SQLAREA和V$SQL兩個檢視的不同之處在於,V$SQL中為每一條SQL保留一個條目,而V$SQLAREA中根據SQL_TEXT進行GROUP  BY,通過version_count計運算元指標的個數。