文章很多摘錄了
http://blog.163.com/liaoxiangui@126/blog/static/7956964020131069843572/
同時基於這篇文章的基礎上,補充一些學習要點,如有問題,希望指出探討.
1 ORACLE體系結構
下圖描述了oracle的體系結構。SGA(system global area)是各個進程共享的內存塊,Buffer cache用來緩存數據文件的數據塊(block)。
2 如何在data buffer中查找數據塊
data buffer存在的意義就是為了在內存中進行高速的數據查找和更新,盡量減少磁盤的IO操作, Buffer Cache中存在一個Hash Bucket結構,將數據庫中已經讀取的數據塊放到里面,在從數據庫文件中讀取到一個數據塊后,Oracle會根據這個數據塊的文件編號,段編號,數據塊號組合到一起通過一個內部的hash算法運算后,會放到不同的hash bucket中,每個Hash Bucket都有一個Hash chain list,保留Buffer Header中的信息,然后通過這個list,把相同hash值的Buffer串起來.結構如圖:
為保護這個結構不受同步更新的破壞,Oracle設計了一個CBC latch的鎖結構(Cache Buffers Chains),一個latch保護32的桶(Bucket),所以為了訪問hash列表,必須先獲得CBC Latch.先獲取Latch后,在對Buffer進行操作.
對buffer操作是一個比較耗時的操作,比如從磁盤讀取block。由於一個latch管理32個桶,所以對buffer操作時不能繼續持有latch。Oracle使用兩階段加latch鎖的方式解決這個問題。
1) 加latch鎖
2) 查找buffer,並對buffer加pin鎖,如果是讀取操作則為shared pin,若是寫操作,則是exclusive pin
3) 釋放latch鎖
4) 對buffer進行操作
5) 加latch鎖
6) 釋放pin鎖
7) 釋放latch鎖
參見下圖,一個buffer擁有一個pin鎖,每個pin鎖對應兩個列表,user's list(擁有鎖的用戶列表)和waiter's list(等待鎖的列表),如果用戶等待pin鎖的時間超過1秒,則認為出現死鎖。buffer和pin鎖是一一對應關系。
加上鎖保護以后,整體的圖
所以讀取數據塊的過程如下,當一個用戶進程想要訪問一個數據塊:
- 根據數據文件號,塊號生成hash 值
- 在Hash Table中找到bucket地址
- 獲取CBC latch,一個latch保護32個bucket
- bucket有一個指針指向bh(buffer head)
- 根據bh的搜索鏈表
- 匹配bh里面的內容和要找的
- 匹配到一個bh,給bh加鎖buffer pin(共享和獨占)
- 釋放cbc latch, cbc latch做兩件事情:保護鏈表,保護加buffer pin鎖
- 進程根據bh里面的ba地址找內存塊
- 找完了,獲得cbc latch,在保護下 釋放buffer pin鎖
利用哈希表管理已經被緩存的data block。為了使得哈希桶上鏈表盡量短,理想目標是一個鏈表上最多只有一個buffer,哈希桶的數量一般是data buffer數量的兩倍,並把桶分組,每個組對應一個latch,latch用來保護桶中的鏈表。一個latch保護32個桶。
雖然和lock相比,latch是輕量級的,但是使用latch也是有比較大的開銷的,所以oracle盡量減少latch數量,所以一個latch管理了高達32個桶。
問題: Oracle如何知道我們需要找的是哪個或者哪些數據塊?
=============================================
數據庫並不知道,根據sql語句執行計划來判斷是走索引還是全表掃描
如果是索引,能直接返回塊號,如果走全表掃描,數據庫知道每個表段最開始的block號.所以我理解從表的第一個block開始尋找.
2粒度
oracle分配內存的單位是granule,一般是16MB。granule中有兩種數據,data buffer和buffer header。data buffer是數據文件的data block在內存中的鏡像,一個data buffer對應一個data block。buffer header存儲了data buffer的狀態和一些指向其它buffer header的指針,buffer header和data buffer是一一對應關系。由於指針在不同平台上大小不一樣,所以buffer header的大小不固定,一般是150--250字節。
注意,buffer header與buffer header的關系不是block header與block的關系。為什么不把buffer header和data buffer合並在一起?因為把buffer header和data buffer分開,可以使得buffer的地址在一個固定的邊界,這樣會使得從磁盤讀取數據時效率更高。
3 cache的種類
buffer cache中有8個cache,各對應一個參數,見下表。
db_cache_size | system,sysaux,temporary表空間使用 |
db_keep_cache_size | keep池。定義表或索引使用buffer_pool keep子句時,使用這個池。 意思是這個表和索引需要常駐內存。 |
db_recycle_cache_size | recycle池。若在定義表或索引以及其它對象時使用buffer_pool recycle子句,則對象的緩存在這個池。意思是這個對象的數據很少被使用,不宜常駐內存。讀一致性塊的內存也從這個內存池中分配。 |
db_2k_cache_size db_4k_cache_size db_8k_cache_size db_16k_cache_size db_32k_cache_size db_NK_cache_size |
db_NK_cache_size 提供自定義block的選項。db_2k_cache_size用來緩存大小為2K的data block。依次類推,db_32k_cache_size用來緩存大小為32K的data block。另外,8K是oracle默認的block大小。 |
在buffercache中存在兩種鏈表,write list和LRU鏈表。LRU鏈表包含了free buffers(還沒有使用的buffer);pinned buffers(被鎖住的buffer,正在執行讀寫操作);還沒有移到write list的dirty buffers。write list包含了臟塊,需要DBWn進程寫入到數據文件。
4 LRU算法
4.1 REPL鏈表
普通LRU算法的實現原理是每訪問一個buffer,就把這個buffer移到MRU端。由於在oracle內部,訪問buffer的頻率非常高,不斷的移動buffer在LRU中的位置開銷相當大,並且不利於並發。所以oracle對LRU進行了改進。下圖是一個buffer pool中的結構,包含兩個鏈表,REPL和REPLAX。鏈表上的每個buffer header對應一個buffer,每個buffer緩存一個data block。
首先,我們在假設在沒有REPLAX鏈表的情況下,oracle是如何淘汰buffer的。oracle對傳統的LRU算法進行了改進。在每個buffer header增加了一個計數器(稱為touch count,簡稱為TCH)和一個時間戳。每次訪問buffer后,如果上次更新buffer header的TCH是在3秒之前,則對TCH加1,並把buffer header中的時間戳更新為當前時間。
oracle為REPL鏈表增加了一個字段cold_hd,可以理解為cold_hd后面的大部分buffer都是冷塊。以下圖為例,我們現在讀取一個沒有被緩存的data block,需要從REPL鏈表上得到一個buffer來緩存當前的data block。首先從LRU端獲取一個buffer,如果是一個冷快(通過TCH來判斷),則使用這個buffer緩存當前的data block,並把這個buffer header移到cold_hd的位置,更新TCH為1;如果是一個熱快(通過TCH來判斷),把這個buffer header移到MRU端,並更新TCH為原來的一半,然后繼續檢查LRU端的buffer是否是一個冷塊,直到找到一個冷塊為止。
LRU淘汰算法
curr_buffer_header=prv_repl label1: while(curr_buffer_header被鎖住(pinned)) curr_buffer_header=curr_buffer_header->prev_buffer if(curr_buffer_header是dirty塊) curr_buffer_header=curr_buffer_header->prev_buffer 把這個buffer移到write list, goto lable1 if(curr_buffer_header是熱快) curr_buffer_header=curr_buffer_header->prev_buffer; 計數器減半; 移動到MRU端; goto lable1; else 移動到cold_hd位置 更新計數器為1 為data buffer加鎖; 從磁盤讀取數據,更新buffer header,更新計數器為1; 為data buffer解鎖;
4.2 LRU進一步優化
在REPL中淘汰一個buffer需要很多操作,比如判斷是否被鎖住,是否是dirty塊。相對來說,REPL淘汰一個buffer是一個重量級操作。為此,oracle 增加了另外一個鏈表REPLAX(replacement list auxilitary),這個鏈表里的所有buffer可以立即被淘汰,不用檢查是否是dirty buffer、是否被pinned。從REPLAX鏈表淘汰buffer是輕量級操作。
oracle啟動時,所有buffer header都掛在REPLAX上,隨着計數器的增加,這些buffer header會被從REPLAX移到REPL中。當DBWn把dirty塊寫入到磁盤后,這些buffer會移到到REPLAX。目前為止,我們還不清楚REPLAX和REPL之間的buffer的移動細節。
7 全表掃描對LRU的影響
對大表全表掃描時,如果上述改進LRU算法的淘汰策略,會導致所有熱快被立即換出。oracle首先會對表大小進行評估,進行大表進行全表掃描時,對大表的buffer采用不同的淘汰策略。
表大小> cache的10%。第一次掃描當作大表來對待,計數器不增加,並且buffer header放入REPLAX鏈表。如果表數據還在緩存的情況下又進行的全部掃描,則認為這個把這個表當作小表掃描來對待,增加計數器。
表大小> cache的25%。計數器的值不會增加,且表對應的buffer header只會出現在REPLAX鏈表上。
在create table時,使用cache子句也會影響buffer的淘汰側路。cache子句包括3個選項CACHE、NOCACHE、CACHE和READS。CACHE適用於經常被訪問的小表,NOCACHE適用於大表和訪問頻次低的表,CACHE READS適用於包含LOB的表。