oracle buffer cache
轉自------------http://blog.csdn.net/robinson1988/article/details/5982996
Buffer Cache 原理
我們在監控等待事件,檢視AWR,ASH報表的時候經常會看到latch: cache buffers chains,有可能還會看到latch: cache buffers lru chain這些等待事件,對於cache buffers chains這個等待事件,相信是大家最為頭疼的,如果對Buffer Cache理解不深,那麼你就遇到這些等待事件就會束手無策。本文的目的就是通過講解Buffer Cache原理,使大家得心應手的處理這些latch爭用。
Buffer Cache概述
Buffer Cache是SGA的一部分,Oracle利用Buffer Cache來管理data block,Buffer Cache的最終目的就是儘可能的減少磁碟I/O。Buffer Cache中主要有3大結構用來管理Buffer Cache。
Hash Bucket & Hash Chain List :Hash Bucket與Hash Chain List用來實現data block的快速定位。
LRU List :掛載有指向具體的free buffer, pinned buffer以及還沒有被移動到 write list的dirty buffer 等資訊。所謂的free buffer就是指沒有包含任何資料的buffer,所謂的pinned buffer,就是指當前正在被訪問的buffer。
Write(Dirty)List :掛載有指向具體的 dirty block的資訊。所謂的dirty block,就是指在 buffer cache中被修改過但是還沒有被寫入到磁碟的block。
Hash Bucket與Hash Chain List
oracle將buffer cache中所有的buffer通過一個內部的Hash演算法運算之後,將這些buffer放到不同的 Hash Bucket中。每一個Hash Bucket中都有一個
Hash Chain List,通過這個list,將這個Bucket中的block串聯起來。
下面舉個簡單的例子來介紹一下Hash 演算法,Oracle的Hash 演算法肯定沒這麼簡單,具體演算法只有Oracle公司知道。
• 一個簡單的mod函式 ,我們去mod 4
Ø 1 mod 4 = 1
Ø 2 mod 4 = 2
Ø 3 mod 4 = 3
Ø 4 mod 4 = 0
Ø 5 mod 4 = 1
Ø 6 mod 4 = 2
Ø 7 mod 4 = 3
Ø 8 mod 4 = 0
……………省略…………………..
那麼這裡就相當於建立了4個Hash Bucket
如果有如下block:
blcok :DBA(1,1) ------> (1+1) mod 4 =2
block :DBA(1,2) ------> (1+2) mod 4 =3
block :DBA(1,3) ------> (1+3) mod 4 =0
block :DBA(1,4) ------> (1+4) mod 4 =1
block :DBA(1,5) ------> (1+5) mod 5 =2
………........省略…………………....
比如我要訪問block(1,5),那麼我對它進行Hash運算,然後到Hash Bucket為2的這個Bucket裡面去尋找,Hash Bucket 為2的這個Bucket 現在有2個block,
這2個block是掛在Hash Chain List上面的
Hash Chain List到底是怎麼組成的呢?這裡我們就要提到x$bh這個基表了
SQL> desc x$bh
Name Null? Type
----------------------- - ----------------
ADDR RAW(8) ---block在buffer cache中的address
INDX NUMBER
INST_ID NUMBER
HLADDR RAW(8) --latch:cache buffers chains 的address
BLSIZ NUMBER
NXT_HASH RAW(8) ---指向同一個Hash Chain List的下一個block
PRV_HASH RAW(8) ---指向同一個Hash Chain List的上一個block
NXT_REPL RAW(8)---指向LRU list中的下一個block
PRV_REPL RAW(8)---指向LRU list中的上一個block
………………省略…………………………
Hash Chain List就是由x$bh中的NXT_HASH,PRV_HASH 這2個指標構成了一個雙向連結串列,其示意圖如下:
通過NXT_HASH,PRV_HASH這2個指標,那麼在同一個Hash Chain List的block就串聯起來了。
理解了Hash Bucket 和 Hash Chain List,我們現在來看看
Hash Bucket 與 Hash Chain List管理Buffer Cache 的結構示意圖
從圖中我們可以看到,一個latch:cache buffers chains(x$bh.hladdr) 可以保護多個Hash Bucket,也就是說,如果我要訪問某個block,我首先要獲得這個latch,一個Hash Bucket對應一個Hash Chain List,而這個
Hash Chain List掛載了一個或者多個Buffer Header。
Hash Bucket的數量受隱含引數_db_block_hash_buckets的影響,
Latch:cache buffers chains的數量受隱含引數_db_block_hash_latches的影響
該隱含引數可以通過如下查詢檢視:
SQL> select * from v$version;
BANNER
------------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bi
SQL> SELECT x.ksppinm NAME, y.ksppstvl VALUE, x.ksppdesc describ
2 FROM x$ksppi x, x$ksppcv y
3 WHERE x.inst_id = USERENV ('Instance')
4 AND y.inst_id = USERENV ('Instance')
5 AND x.indx = y.indx
6 AND x.ksppinm LIKE '%_db_block_hash%'
7 /
NAME VALUE DESCRIB
------------------------- --------------- --------------------------------------
_db_block_hash_buckets 524288 Number of database block hash buckets
_db_block_hash_latches 16384 Number of database block hash latches
_db_block_hash_buckets 該隱含引數在8i以前 等於db_block_buffers/4的下一個素數
到了8i 該引數等於 db_block_buffers*2 ,
到了9i 以後,該引數取的是小於且最接近 db_block_buffers*2 的一個素數
_db_block_hash_latches 該隱含引數表示的是 cache buffers chains latch的數量,它怎麼計算的我們不用深究
可以看到,從8i以後Hash Bucket數量比以前提升了8倍。
可以用下面查詢計算cache buffers chains latch的數量
SQL> select count(*) from v$latch_children a,v$latchname b where a.latch#=b.latch# and b.name='cache buffers chains';
COUNT(*)
----------
16384
還可以用下面查詢計算cache buffers chains latch的數量
SQL> select count(distinct hladdr) from x$bh ;
COUNT(DISTINCTHLADDR)
---------------------
16384
根據我們的查詢,那麼一個cache buffers chains latch 平均下來要管理32個Hash Bucket,那麼現在我們隨意的找一個latch,來驗證一下前面提到的結構圖。
SQL> select * from (select hladdr,count(*) from x$bh group by hladdr) where rownum<=5;
HLADDR COUNT(*)
---------------- ----------
C000000469F08828 15
C000000469F088F0 14
C000000469F089B8 15
C000000469F08A80 24
C000000469F08B48 17
我們查詢latch address 為C000000469F08828 所保護的data block
SQL> select hladdr,obj,dbarfil,dbablk, nxt_hash,prv_hash from x$bh where hladdr='C000000469F08828' order by obj;
HLADDR OBJ DBARFIL DBABLK NXT_HASH PRV_HASH
---------------- ---------- ---------- ---------- ---------------- ----------------
C000000469F08828 2 388 322034 C0000004686ECBD0 C00000017EF8D658
C000000469F08828 2 388 396246 C0000004686ECA60 C0000004686ECA60
C000000469F08828 18 411 674831 C0000004686ECC00 C0000004686ECC00
C000000469F08828 216 411 438948 C0000004686ECBB0 C0000004686ECBB0
C000000469F08828 216 220 100217 C0000004686ECAA0 C0000004686ECAA0
C000000469F08828 216 220 60942 C000000151FB5DD8 C0000004686ECBD0
C000000469F08828 569 411 67655 C00000011FF81668 C0000001E8FB7AC0
C000000469F08828 569 280 1294 C0000004686ECB60 C000000177F9F078
C000000469F08828 58744570 210 332639 C000000177F9F078 C0000004686ECB60
C000000469F08828 65178270 254 408901 C0000004686ECBF0 C0000004686ECBF0
C000000469F08828 65347592 84 615093 C0000004686ECB90 C0000004686ECB90
C000000469F08828 65349200 765 1259399 C0000004686ECA70 C0000004686ECA70
請觀察DBA(388,396246),它的NXT_HASH與PRV_HASH相同,也就是說DBA(388,396246)掛載在只包含有1個data block的 Hash Chain上。
另外也請注意,我通過count(*)計算出來的時候有15個block,但是查詢之後就變成了12個block,那說明有3個block被刷到磁碟上了。
當一個使用者程序想要訪問Block(569,411):
l 對該Block運用Hash演算法,得到Hash值。
l 獲得cache buffers chains latch
l 到相應的Hash Bucket中搜尋相應Buffer Header
l 如果找到相應的Buffer Header,然後判斷該Buffer的狀態,看是否需要構造CR Block,或者Buffer處於pin的狀態,最後讀取。
l 如果找不到,就從磁碟讀入到Buffer Cache中。
在Oracle9i以前,如果其它使用者程序已經獲得了這個latch,那麼新的程序就必須等待,直到該使用者程序搜尋完畢(搜尋完畢之後就會釋放該latch)。從Oracle9i開始 cache buffers chains latch可以只讀共享,也就是說使用者程序A以只讀(select)的方式訪問Block(84,615093),這個時候獲得了該latch,同時使用者程序B也以只讀的方式訪問Block(765,1259399),那麼這個時候由於是隻讀的訪問,使用者程序B也可以獲得該latch。但是,如果使用者程序B要以獨佔的方式訪問Block(765,1259399),那麼使用者程序B就會等待使用者程序A釋放該latch,這個時候Oracle就會對使用者程序B標記一個latch:cache buffers chains的等待事件。
我們遇到了latch:cache buffers chains該怎麼辦?
l 不夠優化的SQL。大量邏輯讀的SQL語句就有可能產生非常嚴重的latch:cache buffers chains等待,因為每次要訪問一個block,就需要獲得該latch,由於有大量的邏輯讀,那麼就增加了latch:cache buffers chains爭用的機率。
Ø 對於正在執行的SQL語句,產生非常嚴重的latch:cache buffers chains爭用,可以利用下面SQL檢視執行計劃,並設法優化SQL語句。
select * from table(dbms_xplan.display_cursor('sql_id',sql_child_number));
Ø 如果SQL已經執行完畢,我們就看AWR報表裡面的SQL Statistics->SQL ordered by Gets->Gets per Exec,試圖優化這些SQL。
l 熱點塊爭用。
Ø 下面查詢查出Top 5 的爭用的latch address。
select * from( select CHILD#,ADDR,GETS ,MISSES,SLEEPS from v$latch_children where name = 'cache buffers chains' and misses>0 and sleeps>0 order by 5 desc, 1, 2, 3) where rownum<6;
Ø 然後利用下面查詢找出Hot block。
select /*+ RULE */
e.owner ||'.'|| e.segment_name segment_name,
e.extent_id extent#,
x.dbablk - e.block_id + 1 block#,
x.tch, /* sometimes tch=0,we need to see tim */
x.tim ,
l.child#
from
v$latch_children l,
x$bh x,
dba_extents e
where
x.hladdr = '&ADDR' and
e.file_id = x.file# and
x.hladdr = l.addr and
x.dbablk between e.block_id and e.block_id + e.blocks -1
order by x.tch desc ;
l Hash Bucket太少,需要更改_db_block_hash_buckets隱含引數。其實在Oracle9i之後,我們基本上不會遇到這個問題了,除非遇到Bug。所以這個是不推薦的,記住,在對Oracle的隱含引數做修改之前一定要諮詢Oracle Support。
LRU List與LRU Write List
前面已經提到過了,如果一個使用者程序發現某個block不在Buffer Cache中,那麼使用者程序就會從磁碟上將這個block讀入Buffer Cache。在將block讀入到Buffer Cache之前,首先要在LRU list上尋找Free的buffer,在尋找過程中,如果發現了Dirty Buffer就將其移動到LRU Write List上。如果Dirty Queue超過了閥值25%(如下面查詢所示),那麼DBWn就會將Dirty Buffer寫入到磁碟中。
SQL> select kvittag,kvitval,kvitdsc from x$kvit where kvittag in('kcbldq','kcbfsp');
KVITTAG KVITVAL KVITDSC
-------------------- ---------- -------------------------------------------------------
kcbldq 25 large dirty queue if kcbclw reaches this
kcbfsp 40 Max percentage of LRU list foreground can scan for free
根據上面的查詢我們還知道,當某個使用者程序掃描LRU list超過40%都還沒找到Free Buffer,那麼這個時候使用者程序將停止掃描LRU list,同時通知DBWn將Dirty Buffer寫入磁碟,使用者程序也將記錄一個free buffer wait等待事件。如果我們經常看到free buffer wait等待事件,那麼我們就應該考慮加大Buffer Cache了。
從Oracle8i開始,LRU List和Dirty List都增加了輔助List,Oracle將LRU List和LRU Write List統稱為Working Set(WS)。每個WS中都包含了幾個功能不同的List,每個WS都會有一個Cache Buffers LRU chain Latch的保護(知識來源於DSI405)。如果資料庫設定了多個DBWR,資料庫會存在多個WS,如果Buffer Cache中啟用了多快取池(default,keep,recycle)時,每個獨立的緩衝池都會有自己的WS。那麼下面我們來查詢一下,以驗證上述理論:
SQL> SELECT x.ksppinm NAME, y.ksppstvl VALUE, x.ksppdesc describ
2 FROM x$ksppi x, x$ksppcv y
3 WHERE x.inst_id = USERENV ('Instance')
4 AND y.inst_id = USERENV ('Instance')
5 AND x.indx = y.indx
6 AND x.ksppinm LIKE '%db_block_lru_latches%'
7 /
NAME VALUE DESCRIB
------------------------------ ---------- -----------------------------
_db_block_lru_latches 32 number of lru latches
SQL> show parameter db_writer
NAME TYPE VALUE
----------------------------- -------------- ------
db_writer_processes inte 2
SQL> show parameter cpu_count
NAME TYPE VALUE
------------------------------------ -------------------- ------------
cpu_count integer 8
我們查到有32個Cache Buffers LRU chain Latch,從Oracle9i開始,_db_block_lru_latches是CPU_COUNT的4倍,如果DB_WITER_PROCESS小於4,置於DB_WITER_PROCESS大於四這個不知道,另外也沒見過哪個資料庫引數的DB_WITER_PROCESS大於4那我們來查詢一下有多少個Working Set:
SQL> select count(*) from x$kcbwds;
COUNT(*)
----------
32
我們查詢到有32個WS,並不代表資料庫就一定使用了這32個WS,有些WS 是資料庫預分配的,這樣在我們啟用Keep pool, recycle pool的時候就不用重啟資料庫了。
那麼我們這裡就只是用了4個WS。
SQL> select count(*) from x$kcbwds where CNUM_REPL>0;
COUNT(*)
----------
4
上面查詢了多次X$KCBWDS基表,現在我們來看看這個基表的主要欄位
ADDR RAW(4) ------address
INDX NUMBER
INST_ID NUMBER --------instance number
SET_ID NUMBER --------work set id
DBWR_NUM NUMBER ------DBWR編號
BLK_SIZE NUMBER ----------WORKSET的BLOCK SIZE
PROC_GROUP NUMBER
CNUM_SET NUMBER
FLAG NUMBER
CKPT_LATCH RAW(4) CHECKPOINT LATCH
CKPT_LATCH1 RAW(4)
SET_LATCH RAW(4) NEXT REPLACEMENT CHAIN
NXT_REPL RAW(4) PRV REPLACEMENT CHAIN
PRV_REPL RAW(4) REPLACEMENT AUX CHAIN
NXT_REPLAX RAW(4)
PRV_REPLAX RAW(4)
CNUM_REPL NUMBER REPLACEMENT CHIAN上的BLOCK數
ANUM_REPL NUMBER AUX CHAIN上的BLOCK 數
COLD_HD RAW(4) COLD HEAD的地址
HBMAX NUMBER HOT端的最大BUFFER數量
HBUFS NUMBER HOT端的當前BUFFER數量
NXT_WRITE RAW(4) LRU-W鏈
PRV_WRITE RAW(4) LRU-W鏈
NXT_WRITEAX RAW(4) LRU-W AUX鏈
PRV_WRITEAX RAW(4) LRU-W AUX鏈
CNUM_WRITE NUMBER LRU-W的BUFFER數
ANUM_WRITE NUMBER LRU-W AUX的BUFFER數
NXT_XOBJ RAW(4) REUSE OBJ鏈(當TRUNCATE,DROP等操作時使用)
PRV_XOBJ RAW(4) REUSE OBJ鏈
NXT_XOBJAX RAW(4) REUSE OBJ AUX鏈
PRV_XOBJAX RAW(4)
CNUM_XOBJ NUMBER
ANUM_XOBJ NUMBER
NXT_XRNG RAW(4) reuse range鏈(TABLESPACE OFFLINE等操作的時候使用)
PRV_XRNG RAW(4)
NXT_XRNGAX RAW(4) REUSE RANGE AUX鏈
PRV_XRNGAX RAW(4)
請注意紅色欄位,正是由於紅色欄位,以及前面提到過的x$bh中的NXT_REPL,PRV_REPL 欄位形成了LRU List 以及LRU Write List。
下圖就是LRU List的結構示意圖
那增加這些AUX List究竟是幹嘛的呢?在資料庫啟動之後,Buffer首先被存放在LRU AUX List上,使用者程序搜尋Free Buffer就會從LRU AUX List 的末/冷端進行。當這些塊被修改後或者是使用者程序要構造CR塊的時候(要構造CR塊也就表明這個塊不滿足讀一致性,是Dirty的),在LRU AUX List上的Buffer就會被移動到LRU Main List的中間,記住是中間不是頭部也不是末尾,那麼DBWR來搜尋Dirty Buffer就可以從LRU Main List開始(注意:DBWR 來搜尋LRU Main List 是由於增量檢查點導致的),DBWR在搜尋LRU Main List的時候如果發現冷的可以被重複使用的Buffer,就會將其移動到LRU AUX List上,這樣搜尋LRU Main List上的Buffer基本都是Dirty Buffer,提高了搜尋效率。DBWR將搜尋到的Dirty Buffer移動到LRUW Main List,當需要將這個Dirty Buffer寫出的時候,就把這個Dirty Buffer移動到LRUW AUX List,這樣,當DBWR要執行寫出可以從LRUW AUX List寫出,這其實是一個非同步的寫出機制。(知識來源Metalink:157868.1)
根據上面的講解,當用戶程序要將Block從磁碟讀入到Buffer Cache中需要獲得Cache Buffers LRU chain Latch,或者是DBWR掃描LRU Main List的時候要獲得Cache Buffers LRU chain Latch。
所以,當我們發現AWR報表上面Cache Buffers LRU chain Latch排名很靠前,那麼我們可以採取如下方法:
l 加大Buffer Cache,過小的Buffer Cache導致大量的磁碟I/O,必然引發
Cache Buffers LRU chain Latch競爭。
l 優化具有大量全表掃描,高磁碟I/O的SQL。如果SQL效率很低,大量的全表掃描,或者掃描沒有選擇性的索引就會引發這個問題。
l 使用多緩衝池技術,把Hot Segments Keep起來,Hot Segments的資訊可以從AWR 報表中的Segments Statistics中得到。
關於Buffer Cache就只能到這裡了,不能再深入了,資料實在太少太凌亂。