Oracle中讀取數據一些原理研究


文章很多摘錄了

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的表。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM