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) --
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 --
[[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計運算元指標的個數。