LUR list & dirty list

LUR list & dirty list

LRUW (LRU write list, also called the“dirty list”), maintaining current (dirty) buffers–

LRU (least recently used list),maintaining the remaining buffers

當一個Server進程需要讀取數據到buffer cache中時:
1).首先要判斷該數據是否存在於buffer中,如果存在,且可用.則獲取數據,再根據LRU算法在LRU列表上移動該block;
2).如果數據不存在於buffer中,Server進程就要掃描lru列表,查找可用的buffer空間(free buffer)以放數據到buffer中,
在掃描lru list的過程中,如果碰到已經被修改過的buffer,就將它移動到dirty list(checkpoint queue)上(由於增量檢查點的引入,DBWR進程也會主動掃描一定比例的LRU list,將發現的髒數據塊移動到dirty lis);
如果dirty list(checkpoint queue)達到了閾值,Server進程就會通知DBWn進程寫出髒數據到數據文件(DBWR進程寫的一個觸發條件);如果Server進程掃描lru列表到一個閾值還沒有找到足夠的free buffer,這時,就停止對lru的掃描轉而通知DBWR寫出髒數據,釋放內存空間;這時,進程處於free buffer wait等待.
3).找到足夠的buffer之後,Server進程就可以將數據從數據文件讀入到buffer cache中;
4).如果讀取的block不滿足"一致性"需求,則Server進程就需要通過當前block的版本從回滾段中讀取該block的"一致性"鏡像返回給用戶(consistent gets).


1.ORACLE通過hash block head address得到的HASH值放入不同的BUCKET,找任何一個BLOCK只需要通過相同的HASH值去指定的BUCKT查找。我不知道你是否有動手寫過C語言的鏈表結構,不管是單鏈表還是雙鏈表,型環鏈表, 掛在一個BUCKET下的BLOCK都是通過連接串起來。

2.寫入就變FREE了。表示這個BUFFER放回LRU LIST。

3.所謂一致性是一個時間點下的數據。 一個事務進來了,如果要查找的塊的SCN大於這個事務進來的或者這個塊明顯有事務,說明不能直接取這個塊,因爲這個事務進來後數據發生變化,只能通過UNDO和BLOCK得到這個事務進來時候的數據或者之前的數據。

LRU LIST僅僅是放空閒塊,DIRTY LIST屬於CHECKPOINT QUEUE, 這個地方會放髒塊,還一個MAIN LIST是放使用的BUFFER,但是由於髒塊並不是立刻進入DIRTY LIST,而是依靠DBWR來發現,所以MAIN LIST也會有髒塊。
1.BUFFER CACHE是一個結構,不是單獨的鏈表,單獨的鏈表有什麼意義呢? 我們不要光從ORACLE去考慮這個問題,UNIX BUFFER結構也是這樣的。既然是BUFFER,就有使用,就有髒塊,就有空閒塊,這些所有的結構構成了我們認爲的BUFFER CACHE結構。

簡單的理解分爲三種鏈表,一種的空閒塊,可以提供使用的,鏈表就是LRU LIST, 一種是正在使用的塊(包過髒塊,因爲髒塊不可能以修改就立刻進入LRUW LIST),鏈表是 *.MAIN LIST, 還有一種是髒塊,也就等待寫入DATAFILE的,LRUW LIST(或者說dirty list, checkpoint queue)


################################################################################

Buffer Cache 原理

                                                                

我們在監控等待事件,查看AWRASH報表的時候經常會看到latch: cache buffers chains,有可能還會看到latch: cache buffers lru chain這些等待事件,對於cache buffers chains這個等待事件,相信是大家最爲頭疼的,如果對Buffer Cache理解不深,那麼你就遇到這些等待事件就會束手無策。本文的目的就是通過講解Buffer Cache原理,使大家得心應手的處理這些latch爭用。

Buffer Cache概述

Buffer CacheSGA的一部分,Oracle利用Buffer Cache來管理data blockBuffer Cache的最終目的就是儘可能的減少磁盤I/OBuffer Cache中主要有3大結構用來管理Buffer Cache

Hash Bucket & Hash Chain List Hash BucketHash Chain List用來實現data block的快速定位

LRU List :掛載有指向具體的free buffer, pinned buffer以及還沒有被移動到 write listdirty buffer 等信息。所謂的free buffer就是指沒有包含任何數據的buffer,所謂的pinned buffer,就是指當前正在被訪問的buffer

Write(Dirty)List :掛載有指向具體的 dirty block的信息。所謂的dirty block,就是指在 buffer cache中被修改過但是還沒有被寫入到磁盤的block

 

Hash BucketHash Chain List

Oraclebuffer cache中所有的buffer通過一個內部的Hash算法運算之後,將這些buffer放到不同的 Hash Bucket。每一個Hash Bucket中都有一個

Hash Chain List,通過這個list,將這個Bucket中的block串聯起來。

下面舉個簡單的例子來介紹一下Hash 算法,OracleHash 算法肯定沒這麼簡單,具體算法只有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

……………省略………………….. 

那麼這裏就相當於創建了4Hash 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 Bucket2的這個Bucket裏面去尋找,Hash Bucket 2的這個Bucket 現在有2block

2block是掛在Hash Chain List上面的

Hash Chain List到底是怎麼組成的呢?這裏我們就要提到x$bh這個基表

SQL> desc x$bh

 Name               Null?    Type

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

 ADDR                       RAW(8)  ---blockbuffer 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_HASHPRV_HASH 2個指針構成了一個雙向鏈表,其示意圖如下:

 


 

spacer.gif

    通過NXT_HASHPRV_HASH2個指針,那麼在同一個Hash Chain Listblock就串聯起來了

理解了Hash Bucket  Hash Chain List,我們現在來看看

Hash Bucket  Hash Chain List管理Buffer Cache 的結構示意圖

 

 

spacer.gif


從圖中我們可以看到,一個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_b                 _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 平均下來要管理32Hash 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_HASHPRV_HASH相同,也就是說DBA(388,396246)掛載在只包含有1data block Hash Chain上。

另外也請注意,我通過count(*)計算出來的時候有15block,但是查詢之後就變成了12block,那說明有3block被刷到磁盤上了。

當一個用戶進程想要訪問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 ListLRU Write List

前面已經提到過了,如果一個用戶進程發現某個block不在Buffer Cache中,那麼用戶進程就會從磁盤上將這個block讀入Buffer Cache。在將block讀入到Buffer Cache之前,首先要在LRU list上尋找Freebuffer,在尋找過程中,如果發現了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,同時通知DBWnDirty Buffer寫入磁盤,用戶進程也將記錄一個free buffer wait等待事件。如果我們經常看到free buffer wait等待事件,那麼我們就應該考慮加大Buffer Cache了。

Oracle8i開始,LRU ListDirty List都增加了輔助ListOracleLRU ListLRU 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

我們查到有32Cache Buffers LRU chain Latch,從Oracle9i開始,_db_block_lru_latchesCPU_COUNT4倍,如果DB_WITER_PROCESS小於4,置於DB_WITER_PROCESS大於四這個不知道,另外也沒見過哪個數據庫參數的DB_WITER_PROCESS大於4那我們來查詢一下有多少個Working Set:

SQL> select count(*) from x$kcbwds;

 

  COUNT(*)

----------

        32

我們查詢到有32WS,並不代表數據庫就一定使用了這32WS,有些WS 是數據庫預分配的,這樣在我們啓用Keep pool, recycle pool的時候就不用重啓數據庫了。

那麼我們這裏就只是用了4WS

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  ----------WORKSETBLOCK 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-WBUFFER

 ANUM_WRITE              NUMBER  LRU-W AUXBUFFER

 NXT_XOBJ                RAW(4)  REUSE OBJ鏈(當TRUNCATEDROP等操作時使用)

 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_REPLPRV_REPL 字段形成了LRU List 以及LRU Write List

    下圖就是LRU List的結構示意圖

spacer.gif


    

那增加這些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/OSQL。如果SQL效率很低,大量的全表掃描,或者掃描沒有選擇性的索引就會引發這個問題。    

l  使用多緩衝池技術,把Hot Segments Keep起來,Hot Segments的信息可以從AWR 報表中的Segments Statistics中得到。



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