国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發(fā) > 綜合 > 正文

buffer cache深度分析之內(nèi)部管理機制

2024-07-21 02:32:46
字體:
供稿:網(wǎng)友
3.buffer cache的內(nèi)部治理機制
3.1 在buffer cache中獲取所需要的數(shù)據(jù)塊的過程
    當(dāng)前臺進程發(fā)出SELECT或者其他DML語句時,Oracle根據(jù)SQL語句的執(zhí)行計劃所找到的數(shù)據(jù)塊,會構(gòu)造一個名為數(shù)據(jù)塊描述(buffer descriptor)的內(nèi)存結(jié)構(gòu)。該buffer descriptor位于session的PGA中,所包含的內(nèi)容主要是數(shù)據(jù)塊所在的物理地址、數(shù)據(jù)塊的類型、數(shù)據(jù)塊所屬對象的object id等信息。
    隨后,oracle會把對數(shù)據(jù)塊請求的鎖定模式以及所構(gòu)造出來的buffer descriptor傳入專門搜索數(shù)據(jù)塊的函數(shù)中。在該函數(shù)中,oracle根據(jù)buffer descriptor所記錄的信息,應(yīng)用hash算法以后,得到要找的數(shù)據(jù)塊所處的hash bUCket,也就是確定該數(shù)據(jù)塊在哪條hash chain上。然后,oracle進入該hash chain,從上面所掛的第一個buffer header開始搜索,一直搜索到最后一個buffer header。  在hash chain上搜索的邏輯如下:
  1) 比較buffer header上所記錄的數(shù)據(jù)塊的地址,假如不符合,則跳過該buffer header。
  2) 跳過狀態(tài)為CR的buffer header。
  3) 假如碰到狀態(tài)為READING的buffer header,則等待,一直等到該buffer header的狀態(tài)改變以后再比較所記錄的數(shù)據(jù)塊的地址是否符合。
  4) 假如發(fā)現(xiàn)數(shù)據(jù)塊地址符合的buffer header,則查看該buffer header是否位于正在使用的列表上,假如是,則判定已存在的鎖定模式與當(dāng)前所要求的鎖定模式是否兼容,假如是,則返回該buffer header所記錄的數(shù)據(jù)塊地址,并將當(dāng)前進程號放入該buffer header所處的正在使用的列表上。
  5) 假如發(fā)現(xiàn)鎖定模式不兼容,則根據(jù)找到的buffer header所指向的數(shù)據(jù)塊的內(nèi)容,構(gòu)建一個新的、內(nèi)容一樣的、狀態(tài)為XCURRENT的復(fù)制數(shù)據(jù)塊,并且構(gòu)造一個狀態(tài)為CR的buffer header,同時該buffer header指向所新建立的復(fù)制數(shù)據(jù)塊。然后,返回該復(fù)制數(shù)據(jù)塊的地址,并將當(dāng)前進程號放入該buffer header所處的正在使用的列表上。
  6) 假如比較完整個hash chain以后還沒發(fā)現(xiàn)所要找的buffer header,則從磁盤上讀取數(shù)據(jù)文件。并將讀取到的數(shù)據(jù)塊所對應(yīng)的buffer header掛到hash chain上。3.2 LRU和LRUW鏈表結(jié)構(gòu)及其治理機制
  3.2.1 LRU和LRUW鏈表結(jié)構(gòu)概述
    在前面,我們已經(jīng)知道了oracle是如何在hash chain中搜索要找的數(shù)據(jù)塊所對應(yīng)的buffer header的過程,我們也知道假如在hash chain上沒有找到所要的buffer header時,oracle會發(fā)出I/O調(diào)用,到磁盤上的數(shù)據(jù)文件中獲取數(shù)據(jù)塊,并將該數(shù)據(jù)塊的內(nèi)容拷貝一份到buffer cache中的內(nèi)存數(shù)據(jù)塊里(順帶提一句,內(nèi)存數(shù)據(jù)塊通常叫做buffer,而數(shù)據(jù)文件里的數(shù)據(jù)塊通常叫做block,二者是一個意思)。這個時候,假如buffer cache是空的,比較好辦,直接拿一個空的內(nèi)存數(shù)據(jù)塊來用即可。但是假如buffer cache中的內(nèi)存數(shù)據(jù)塊全都被用掉了,沒有空的內(nèi)存數(shù)據(jù)塊了,怎么辦?應(yīng)該重新使用哪一個內(nèi)存數(shù)據(jù)塊?當(dāng)然我們可以一個一個的比較內(nèi)存數(shù)據(jù)塊與其對應(yīng)在數(shù)據(jù)文件中的數(shù)據(jù)塊的內(nèi)容是否一致,假如一致則可以將該數(shù)據(jù)塊拿來,將其內(nèi)容清空,然后拷貝上當(dāng)前數(shù)據(jù)塊的內(nèi)容;假如不一致,則跳過,再找下一個。毫無疑問,這種方式效率低下。為了高效的治理buffer cache中的內(nèi)存數(shù)據(jù)塊,oracle引入了LRU和LRUW等鏈表等結(jié)構(gòu)。   在buffer cache中,最耳熟能詳?shù)逆湵砜赡芫褪荓RU鏈表了。在前面描述buffer cache結(jié)構(gòu)的圖上,也可以看到有兩個鏈表:LRU和LRUW。在介紹LRU和LRUW前,先說明幾個概念。
    1) 臟數(shù)據(jù)塊(dirty buffer):buffer cache中的內(nèi)存數(shù)據(jù)塊的內(nèi)容與數(shù)據(jù)文件中的數(shù)據(jù)塊的內(nèi)容不一致。
    2) 可用數(shù)據(jù)塊(free buffer):buffer cache中的內(nèi)存數(shù)據(jù)塊為空或者其內(nèi)容與數(shù)據(jù)文件中的一致。注重,可用數(shù)據(jù)塊不一定是空的。
    3) 釘住的數(shù)據(jù)塊(ping buffer):當(dāng)前正在更新的內(nèi)存數(shù)據(jù)塊。
    4) 數(shù)據(jù)庫寫進程(DBWR):這是一個很底層的數(shù)據(jù)庫后臺進程。既然是后臺進程,就表示該進程是不能被用戶調(diào)用的。由oracle內(nèi)置的一些事件根據(jù)需要啟動該進程,該進程用來將臟數(shù)據(jù)塊寫入磁盤上的數(shù)據(jù)文件。
    LRU表示Least Recently Used,也就是指最近最少使用的buffer header鏈表。LRU鏈表串連起來的buffer header都指向可用數(shù)據(jù)塊。而LRUW則表示Least Recently Used Write,也叫做dirty list,也就是臟數(shù)據(jù)塊鏈表,LRUW串起來的都是修改過但是還沒有寫入數(shù)據(jù)文件的內(nèi)存數(shù)據(jù)塊所對應(yīng)的buffer header。某個buffer header要么掛在LRU上,要么掛在LRUW上,不能同時掛在這兩個鏈表上。
     隨著硬件技術(shù)的發(fā)展,電腦的內(nèi)存越來越大。buffer cache也是越來越大,只用一條LRU和一條LRUW來治理buffer header已經(jīng)不夠用了。同時oracle還引入了多個DBWR后臺進程來幫助將buffer cache中的臟數(shù)據(jù)塊寫入數(shù)據(jù)文件,顯然,多個DBWR后臺進程都去掃描相同的LRUW鏈表會引起爭用。為此oracle引入了working set的概念。每個working set都具有它自己的一組LRU和LRUW鏈表。每個working set都由一個名為“cache buffers lru chain”的latch(也叫做lru latch)來治理,所以從這個意義上說,每一個lru latch就是一個working set。而每個被加載到buffer cache的buffer header都以輪詢的方式掛到working set上去。也就是說,當(dāng)buffer cache加載一個新的數(shù)據(jù)塊時,其對應(yīng)的buffer header會去找一個可用的lru latch,假如沒有找到,則再找下一個lru latch,直到找到為止。假如輪詢完所有的lru latch也沒能找到可用的lru latch,該進程只有等待latch free等待事件,同時出現(xiàn)在v$session_wait中,并增加“l(fā)atch misses”。假如啟用了多個DBWR后臺進程的話,每個DBWR進程都會對應(yīng)一個不同的working set,而且每個DBWR只會處理分配給它的working set,不會處理其他的working set。
    我們已經(jīng)知道一個lru latch就是一個working set,那么working set的數(shù)量也就是lru latch的數(shù)量。而lru latch的數(shù)量是由一個隱藏參數(shù):_db_block_lru_latches決定的。該參數(shù)缺省值為DBWR進程的數(shù)量×8。
 該參數(shù)最小必須為8,假如強行設(shè)置比8小的數(shù)值,oracle將忽略你設(shè)置的值,而使用8作為該參數(shù)值。 1buffer cache深度分析之內(nèi)部治理機制(圖一)SQL> alter system set "_db_block_lru_latches"=1 scope=spfile;2buffer cache深度分析之內(nèi)部治理機制(圖一)SQL> startup force3buffer cache深度分析之內(nèi)部治理機制(圖一)SQL> show parameter _db_block4buffer cache深度分析之內(nèi)部治理機制(圖一)NAME TYPE VALUE5buffer cache深度分析之內(nèi)部治理機制(圖一)------------------------------------ ----------- ------------------------------6buffer cache深度分析之內(nèi)部治理機制(圖一)_db_block_lru_latches integer 83.2.2 深入LRU鏈表
  我們已經(jīng)知道LRU鏈表是用來查找可以重用的內(nèi)存數(shù)據(jù)塊的,那么oracle是怎么使用LRU鏈表的呢?這里需要分為8i之前和8i以后兩種情況。
   在8i之前,我們舉一個例子。假設(shè)buffer cache只能容納4個數(shù)據(jù)塊,同時只有一個hash chain和一個LRU。當(dāng)數(shù)據(jù)庫剛剛啟動,buffer cache是空的。這時前臺進程發(fā)出SELECT語句獲取數(shù)據(jù)塊時,oracle找一個空的內(nèi)存數(shù)據(jù)塊,并將其對應(yīng)的buffer header掛到hash chain上。同時,oracle還會把該buffer header掛到LRU的最尾端。隨后前臺進程又發(fā)出SELECT語句,這時所找到的buffer header在LRU上會掛到前一個buffer header的后面,也就是說第二次SELECT語句所找到的buffer header現(xiàn)在變成了LRU的最尾端了。假設(shè)發(fā)出4句SELECT以后找到了4個buffer header,從而用完了所有的buffer cache空間。這個時候的LRU可以用下圖二來表示。
  buffer cache深度分析之內(nèi)部治理機制(圖二)     這個時候,發(fā)來了第五句SELECT語句。這時的buffer cache里已經(jīng)沒有空的內(nèi)存數(shù)據(jù)塊了。但是既然需要容納下第五個數(shù)據(jù)塊,就必然需要找一個可以被替換(后面會看到類似犧牲、重用的字樣,它們和替換都是一個意思)的內(nèi)存數(shù)據(jù)塊。這個內(nèi)存數(shù)據(jù)塊會到LRU上去找。按照oracle設(shè)定的最近最少使用的原則,位于LRU最尾端的BH1將成為犧牲者,oracle會把該BH1對應(yīng)的內(nèi)存數(shù)據(jù)塊的內(nèi)容清空,并將當(dāng)前第五句SQL所獲得的數(shù)據(jù)塊的內(nèi)容拷貝進去。這個時候,BH1就成了LRU的首端,而BH2則成為了LRU的尾端。如下圖三所示。在這種方式下,經(jīng)常被訪問的數(shù)據(jù)塊可以一直靠近LRU的首端,也就保證了這些數(shù)據(jù)塊可以盡可能的不被替換掉,從而保證了訪問的效率。
buffer cache深度分析之內(nèi)部治理機制(圖三)                    圖三
   到了8i以后,oracle引入了一種更加復(fù)雜的機制來治理LRU上的數(shù)據(jù)塊。8i以后,LRU和LRUW鏈表都具有兩個子鏈表,分別叫做輔助鏈表和主鏈表。同時還對buffer header增加了一個屬性:touch數(shù)量,也就是每個buffer header曾經(jīng)被訪問過的次數(shù),來對LRU鏈表進行治理。 oracle每訪問一次buffer header,就會將該buffer header上的touch數(shù)量增加1,因此,touch數(shù)量“近似”的體現(xiàn)了某個內(nèi)存數(shù)據(jù)塊總共被訪問的次數(shù)。注重,這只是近似,并不精確。因為touch的增加并沒有使用latch來治理并發(fā)性。這只是一個大概值,表示趨勢的,不用百分百的精確。
還是用上面的這個例子來說明。還是假設(shè)buffer cache只能容納4個數(shù)據(jù)塊,同時只有一個hash chain和一個LRU(確切的說應(yīng)該是一對LRU主鏈表和輔助鏈表)。讀入第一個數(shù)據(jù)塊時,該數(shù)據(jù)塊對應(yīng)的buffer header會掛到LRU輔助鏈表(注重,這里是輔助鏈表,而不是主鏈表)的最末端,同時touch數(shù)量為1。讀取第二個不同的數(shù)據(jù)塊時,該數(shù)據(jù)塊對應(yīng)的buffer header會掛到前一個buffer header的后面,從而位于LRU輔助鏈表的最末端,同樣touch為1。假設(shè)4個數(shù)據(jù)塊全都用完以后的LRU鏈表可以用下圖四描述。每個buffer header的touch數(shù)量都為1。
buffer cache深度分析之內(nèi)部治理機制(圖四)
  從上圖中我們可以看到輔助LRU鏈表都掛滿了,而主LRU鏈表還是空的。這個時候,前臺發(fā)出第五句SQL語句,要求返回指定的數(shù)據(jù)塊。這時,oracle發(fā)現(xiàn)buffer cache里已經(jīng)沒有空的內(nèi)存數(shù)據(jù)塊了,于是從輔助LRU鏈表的尾部開始掃描,也就是從BH1開始掃描,以查找可以被替代的數(shù)據(jù)塊。掃描的過程中按照下面的邏輯來選擇被犧牲的(也就是可以被替代的)數(shù)據(jù)塊:
1) 假如被掃描到的buffer header的touch數(shù)量小于隱藏參數(shù)_db_aging_hot_criteria(該參數(shù)缺省為2)的值,則選中該buffer header作為犧牲者,并立即返回該buffer header所含有的數(shù)據(jù)塊的地址。
2) 假如當(dāng)前buffer header的touch數(shù)量大于_db_aging_hot_criteria的值,則不會使用該buffer header。但是假如當(dāng)前的_db_aging_stay_count的值小于_db_aging_hot_criteria的值,則會將當(dāng)前該buffer header的touch值賦值給_db_aging_stay_count;否則將當(dāng)前buffer header的touch數(shù)量減掉一半。  按照上述的邏輯,這時將選出BH1作為犧牲者(因為BH1的touch數(shù)量為1,小于_db_aging_hot_criteria
的值),并將其對應(yīng)的內(nèi)存數(shù)據(jù)塊的內(nèi)容清空,同時將當(dāng)前第五個數(shù)據(jù)塊的內(nèi)容拷貝進去。但是這里要注重,這個時候該BH1在LRU鏈表上的位置并不會發(fā)生任何的變化。而不會像8i之前的那樣,BH1變成LRU鏈表的首端。
接下來,前臺發(fā)來了第六句和第七句SQL,分別要返回與第五句和第四句SQL一樣的數(shù)據(jù)塊,也就是要返回當(dāng)前的BH1和BH4。這個時候,oracle會增加BH1和BH4的touch數(shù)量,同時將該BH1和BH4從輔助LRU鏈表上摘下,轉(zhuǎn)移到主LRU鏈表的中間位置。可以用下圖五描述。
  buffer cache深度分析之內(nèi)部治理機制(圖五)                                                                    圖五

   這個時候,假如發(fā)來了第八句SQL,要求返回與第三句SQL相同的數(shù)據(jù)塊,也就是當(dāng)前的BH3,則這時該BH3會插入主LRU鏈表上的BH1和BH4中間,注重每次向主LRU列表插入buffer header時都是向中間位置插入。假如發(fā)來了第九句SQL要求返回BH2,則我們可以知道,BH2會轉(zhuǎn)移到主LRU鏈表的中間。這個時候,輔助LRU鏈表就空了,沒有buffer header了。

    這時,假如又發(fā)來第十句SQL,要求返回一個新的、buffer cache中不存在所需內(nèi)容的數(shù)據(jù)塊時。oracle會先掃描輔助LRU鏈表,發(fā)現(xiàn)上面沒有任何的buffer header時,則必須掃描主LRU鏈表。從尾部開始掃描,采用前面說到的與掃描輔助LRU鏈表相同的規(guī)則挑選犧牲者。挑出的可以被替代的buffer header將從主LRU鏈表上摘下,放入輔助LRU鏈表。

    從上面所描述的buffer header在輔助LRU鏈表和主LRU鏈表之間交替的過程中,我們可以看出,oracle改進LRU鏈表的治理方式的目的,就是想千方百計的能夠?qū)⒍啻伪辉L問的數(shù)據(jù)塊保留在內(nèi)存里,同時又要平衡有限的內(nèi)存資源。這種方式相比較8i之前而言,無疑是進步很多的。在8i之前中,某個數(shù)據(jù)塊可能只會被訪問一次,但是就這么一次的訪問就將該數(shù)據(jù)塊放到了LRU的首端,從而可能就擠掉了一個LRU上不是那么經(jīng)常被訪問,但是也會多次訪問的數(shù)據(jù)塊。而8i以后,將訪問一次的數(shù)據(jù)塊和訪問一次以上的數(shù)據(jù)塊徹底分開,而且查找可用數(shù)據(jù)塊時,始終都是從輔助LRU鏈表開始掃描。實際上也就使得越傾向于只訪問一次的數(shù)據(jù)塊越快的從內(nèi)存中清理出去。3.2.3 LRUW鏈表治理

   從前面我們已經(jīng)知道SELECT語句讀取數(shù)據(jù)塊到buffer cache的過程。那么我們必然會產(chǎn)生另外一個疑問,就是當(dāng)使用DML等語句修改了buffer cache里的內(nèi)存數(shù)據(jù)塊以后的過程是怎樣的?實際上,為了能夠最有效、安全的完成將內(nèi)存數(shù)據(jù)塊寫入數(shù)據(jù)文件的過程,oracle提供了比讀取數(shù)據(jù)塊更為復(fù)雜的機制。

  我們已經(jīng)知道LRUW表示臟數(shù)據(jù)塊鏈表,該鏈表上的buffer header指向的都是已經(jīng)從LRU鏈表上摘下來、其對應(yīng)的內(nèi)存數(shù)據(jù)塊里的內(nèi)容已經(jīng)被修改、但是還沒有被寫入數(shù)據(jù)文件的內(nèi)存數(shù)據(jù)塊。在這些臟數(shù)據(jù)塊在能夠被重用之前,它們必須要被DBWR寫入磁盤。從8i以后,LRUW鏈表同樣包含兩個子鏈表:輔助LRUW鏈表和主LRUW鏈表。那么LRUW鏈表是如何產(chǎn)生buffer header的呢?oracle又是如何對其進行治理的呢?

  我們還是接著上面圖五所示的例子來說明。假設(shè)這個時候,前臺用戶發(fā)出DML語句,要求修改BH2所指向的內(nèi)存數(shù)據(jù)塊。這時,按順序發(fā)生下面的動作:
1) oracle會將BH2從輔助LRU鏈表上摘下,同時插入主LRU鏈表的中間,也就是插入BH1和BH4中間,同時增加BH2的touch的數(shù)量。
2) 將該BH2的標記設(shè)置為釘住(ping)。
3) 更新BH2對應(yīng)的內(nèi)存數(shù)據(jù)塊的內(nèi)容。
4) 更新完以后,取消釘住的標記。
5) 將BH2從主LRU鏈表轉(zhuǎn)移到主LRUW鏈表上。
6) 假如這個時候又有進程發(fā)出更新BH2所對應(yīng)的內(nèi)存數(shù)據(jù)塊的內(nèi)容,則BH2再次被釘住,更新,取消釘住。
7) DBWR啟動以后,在掃描主LRUW鏈表時會將BH2轉(zhuǎn)移到輔助LRUW鏈表上。
8) DBWR將輔助LRUW鏈表上的BH2對應(yīng)的數(shù)據(jù)塊寫入數(shù)據(jù)文件。
9) 確認成功寫入數(shù)據(jù)文件以后,將BH2從輔助LRUW鏈表上轉(zhuǎn)移到輔助LRU鏈表上。

  從上面的描述中,我們可以看到,主LRUW鏈表上包含的buffer header要么是已經(jīng)更新完了的數(shù)據(jù)塊,要么是被釘住正在更新的數(shù)據(jù)塊。而當(dāng)DBWR進程啟動以后,它會掃描主LRUW鏈表,并跳過正在被釘住更新的buffer header,而將已經(jīng)更新完了的buffer header從主LRUW鏈表上摘除,并轉(zhuǎn)移到輔助LRUW鏈表上去。掃描完主LRUW鏈表,或掃描的buffer header的個數(shù)達到一定限度時,DBWR會轉(zhuǎn)到輔助LRUW上,將上面的buffer header所對應(yīng)的數(shù)據(jù)塊寫入數(shù)據(jù)文件。所以說,對于輔助鏈表上的buffer header來說,要么是正在等待被寫入的;要么就是已經(jīng)發(fā)出寫入請求,正在寫入而還沒寫完的。這里要注重的是,buffer header進入LRUW鏈表,是從尾端進入;而DBWR掃描LRUW鏈表時,則是從首端開始。

  順帶提一句,這里將主LRUW鏈表和輔助LRUW鏈表分開,主要就是為了提高DBWR在主LRUW鏈表上掃描的效率。假如只有主LRUW鏈表而沒有輔助LRUW鏈表的話,勢必造成三種類型buffer header交織在LRUW鏈表上:1)正在被釘住更新的buffer header;2)已經(jīng)更新完,而正在等待被寫入數(shù)據(jù)文件的buffer header;3)已經(jīng)發(fā)出寫請求,正在寫而尚未寫完的buffer header。在這種情況下,必然造成DBWR為了找到第二種類型的buffer header而需要掃描不該掃描的第三種類型的buffer header。
3.2.4 DBWR進程
  我們已經(jīng)知道DBWR進程負責(zé)將臟數(shù)據(jù)塊寫入磁盤。它是一個非常重要的進程,在后臺進程中的sid為2,在PMON進程啟動以后隨即啟動。
  buffer cache深度分析之內(nèi)部治理機制(圖一)SQL> select c.sid,a.name,a.descriptionbuffer cache深度分析之內(nèi)部治理機制(圖一) 2 from v$bgPRocess a ,v$process b , v$session cbuffer cache深度分析之內(nèi)部治理機制(圖一) 3 where a.paddr=b.addrbuffer cache深度分析之內(nèi)部治理機制(圖一) 4 and b.addr = c.paddr;buffer cache深度分析之內(nèi)部治理機制(圖一) SID NAME DESCRIPTIONbuffer cache深度分析之內(nèi)部治理機制(圖一)---------- ----- -------------------------------------------buffer cache深度分析之內(nèi)部治理機制(圖一) 1 PMON process cleanupbuffer cache深度分析之內(nèi)部治理機制(圖一) 2 DBW0 db writer process 0buffer cache深度分析之內(nèi)部治理機制(圖一) 3 LGWR Redo etc.buffer cache深度分析之內(nèi)部治理機制(圖一) 4 CKPT checkpointbuffer cache深度分析之內(nèi)部治理機制(圖一)………………………………………………………………………………   隨著內(nèi)存的不斷增加,1個DBWR進程可能不夠用了。所以從8i起,我們可以為系統(tǒng)配置多個DBWR進程。初始化參數(shù):db_writer_processe決定了啟動多少個DBWR進程。每個DBWR進程都會分配一個lru latch,也就是說每個DBWR進程對應(yīng)一個working set。 因此oracle建議配置的DBWR進程的數(shù)量應(yīng)該等于lru latch的數(shù)量,同時應(yīng)該小于CPU的數(shù)量。系統(tǒng)啟動時,就確定好了working set與DBWR進程的對應(yīng)關(guān)系,每個DBWR進程只會將分配給自己的working set上的臟數(shù)據(jù)塊寫入數(shù)據(jù)文件。
  DBWR作為一個后臺進程,只有在某些條件滿足了才會觸發(fā)。這些條件包括:1) 當(dāng)進程在輔助LRU鏈表和主LRU鏈表上掃描以查找可以覆蓋的buffer header時,假如已經(jīng)掃描的buffer header的數(shù)量到達一定的限度(由隱藏參數(shù):_db_block_max_scan_pct決定)時,觸發(fā)DBWR進程。_db_block_max_scan_pct表示已經(jīng)掃描的buffer header的個數(shù)占整個LRU鏈表上buffer header總數(shù)的百分比。這時,搜索可用buffer header的進程掛起,在v$session_wait中表現(xiàn)為等待“free buffer wait”事件,同時增加v$sysstat中的“dirty buffers inspected”的值。

2) 當(dāng)DBWR在主LRUW鏈表上查找已經(jīng)更新完而正在等待被寫入數(shù)據(jù)文件的buffer header時,假如找到的buffer header的數(shù)量超過一定限度(由隱藏參數(shù):_db_writer_scan_depth_pct決定)時,DBWR就不再繼續(xù)往下掃描了,而轉(zhuǎn)到輔助LRUW鏈表上將其上的臟數(shù)據(jù)塊寫入數(shù)據(jù)文件。_db_writer_scan_depth_pct表示已經(jīng)掃描的臟數(shù)據(jù)塊的個數(shù)占整個主LRUW鏈表上buffer header總數(shù)的百分比。

3) 假如主LRUW鏈表和輔助LRUW鏈表上的臟數(shù)據(jù)塊的總數(shù)超過一定限度,也將觸發(fā)DBWR進程。該限度由隱藏參數(shù):_db_large_dirty_queue決定。
4) 發(fā)生增量檢查點(incremental checkpoint)或完全檢查點(complete checkpoint)時觸發(fā)DBWR。
5) 每隔三秒鐘啟動一次DBWR。
6) 將表空間設(shè)置為離線(offline)狀態(tài)時觸發(fā)DBWR。
7) 發(fā)出命令:alter tablespace … begin backup,從而將表空間設(shè)置為熱備份狀態(tài)時觸發(fā)DBWR。
8) 將表空間設(shè)置為只讀狀態(tài)時,觸發(fā)DBWR。
9) 刪除對象時(比如刪除某個表)會觸發(fā)DBWR。
  當(dāng)DBWR要寫臟數(shù)據(jù)塊時,并不是說立即將所有的臟數(shù)據(jù)塊都同時寫入磁盤。為了盡量減少物理的
I/O的次數(shù),DBWR會將要寫的臟數(shù)據(jù)塊所對應(yīng)的buffer header拷貝到一個名為批量寫(write batch)的結(jié)構(gòu)中。每個working set所對應(yīng)的DBWR進程都可以向該結(jié)構(gòu)里拷貝buffer header。當(dāng)write batch的buffer header的個數(shù)達到一定限額時,才會發(fā)生實際的I/O,從而將臟數(shù)據(jù)塊寫入磁盤。這個限額為硬件平臺所能支持的同時并發(fā)的異步I/O的最大數(shù)量。8i之前是可以用隱藏參數(shù)(_db_block_write_batch)來控制這個限額的。但是8i以后,取消了該參數(shù),而由oracle自己來計算。3.2.5 DBWR、CKPT、LGWR進程之間的合作
  將內(nèi)存數(shù)據(jù)塊寫入數(shù)據(jù)文件實在是一個相當(dāng)復(fù)雜的過程,在這個過程中,首先要保證安全。所謂安全,就是在寫的過程中,一旦發(fā)生實例崩潰,要有一套完整的機制能夠保證用戶已經(jīng)提交的數(shù)據(jù)不會丟失;其次,在保證安全的基礎(chǔ)上,要盡可能的提高效率。眾所周知,I/O操作是最昂貴的操作,所以應(yīng)該盡可能的將臟數(shù)據(jù)塊收集到一定程度以后,再批量寫入磁盤中。
 直觀上最簡單的解決方法就是,每當(dāng)用戶提交的時候就將所改變的內(nèi)存數(shù)據(jù)塊交給DBWR,由其寫入數(shù)據(jù)文件。這樣的話,一定能夠保證提交的數(shù)據(jù)不會丟失。但是這種方式效率最為低下,在高并發(fā)環(huán)境中,一定會引起I/O方面的爭用。oracle當(dāng)然不會采用這種沒有擴展性的方式。oracle引入了CKPT和LGWR這兩個后臺進程,這兩個進程與DBWR進程互相合作,提供了既安全又高效的寫臟數(shù)據(jù)塊的解決方法。

  用戶進程每次修改內(nèi)存數(shù)據(jù)塊時,都會在日志緩沖區(qū)(redo buffer)中構(gòu)造一個相應(yīng)的重做條目(redo entry),該重做條目描述了被修改的數(shù)據(jù)塊在修改之前和修改之后的值。而LGWR進程則負責(zé)將這些重做條目寫入聯(lián)機日志文件。只要重做條目進入了聯(lián)機日志文件,那么數(shù)據(jù)的安全就有保障了,否則這些數(shù)據(jù)都是有安全隱患的。LGWR 是一個必須和前臺用戶進程通信的進程。LGWR 承擔(dān)了維護系統(tǒng)數(shù)據(jù)完整性的任務(wù),它保證了數(shù)據(jù)在任何情況下都不會丟失。

  LGWR將重做條目寫入聯(lián)機日志文件的情況分兩種:后臺寫(background write)和同步寫(sync write)。觸發(fā)后臺寫的條件有四個:1)每隔三秒鐘,LGWR啟動一次;2)在DBWR啟動時,假如發(fā)現(xiàn)臟數(shù)據(jù)塊所對應(yīng)的重做條目還沒有寫入聯(lián)機日志文件,則DBWR觸發(fā)LGWR進程并等待LRWR寫完以后才會繼續(xù);3)重做條目的數(shù)量達到整個日志緩沖區(qū)的1/3時,觸發(fā)LGWR;4)重做條目的數(shù)量達到1MB時,觸發(fā)LGWR。而觸發(fā)同步寫的條件就一個:當(dāng)用戶提交(commit)時,觸發(fā)LGWR。

  假如DBWR在寫臟數(shù)據(jù)塊的過程中,忽然發(fā)生實例崩潰。我們已經(jīng)知道,用戶提交時,oracle是不一定會把提交的數(shù)據(jù)塊寫入數(shù)據(jù)文件的。那么實例崩潰時,必然會有一些已經(jīng)提交但是還沒有被寫入數(shù)據(jù)文件的內(nèi)存數(shù)據(jù)塊丟失了。當(dāng)實例再次啟動時,oracle需要利用日志文件中記錄的重做條目在buffer cache中重新構(gòu)造出被丟失的數(shù)據(jù)塊,從而完成前滾和回滾的工作,并將丟失的數(shù)據(jù)塊找回來。于是這里就存在一個問題,就是oracle在日志文件中找重做條目時,到底應(yīng)該找哪些重做條目?換句話說,應(yīng)該在日志文件中從哪個起點開始往后應(yīng)用重做條目?注重,這里所指的日志文件可能不止一個日志文件。

  因為oracle需要隨時預(yù)防可能的實例崩潰現(xiàn)象,所以oracle在數(shù)據(jù)庫的正常運行過程中,會不斷的定位這個起點,以便在不可預(yù)期的實例崩潰中能夠最有效的保護并恢復(fù)數(shù)據(jù)。同時,這個起點的選擇非常有講究。首先,這個起點不能太靠前,太靠前意味著要處理很多的重做條目,這樣會導(dǎo)致實例再次啟動時所進行的恢復(fù)的時間太長;其次,這個起點也不能太靠后,太靠后說明只有很少的臟數(shù)據(jù)塊沒有被寫入數(shù)據(jù)文件,也就是說前面已經(jīng)有很多臟數(shù)據(jù)塊被寫入了數(shù)據(jù)文件,那也就意味著只有在DBWR啟動的很頻繁的情況下,才能使得buffer cache中所殘留的臟數(shù)據(jù)塊的數(shù)量很少。但很明顯,DBWR啟動的越頻繁,那么所占用的寫數(shù)據(jù)文件的I/O就越嚴重,那么留給其他操作(比如讀取buffer cache中不存在的數(shù)據(jù)塊等)的I/O資源就越少。這顯然也是不合理的。

  從這里也可以看出,這個起點實際上說明了,在日志文件中位于這個起點之前的重做條目所對應(yīng)的在buffer cache中的臟數(shù)據(jù)塊已經(jīng)被寫入了數(shù)據(jù)文件,從而在實例崩潰以后的恢復(fù)中不需要去考慮。而這個起點以后的重做條目所對應(yīng)的臟數(shù)據(jù)塊實際還沒有被寫入數(shù)據(jù)文件,假如在實例崩潰以后的恢復(fù)中,需要從這個起點開始往后,依次取出日志文件中的重做條目進行恢復(fù)。考慮到目前的內(nèi)存容量越來越大,buffer cache也越來越大,buffer cache中包含幾百萬個內(nèi)存數(shù)據(jù)塊也是很正常的現(xiàn)象的前提下,如何才能最有效的來定位這個起點呢?   為了能夠最佳的確定這個起點,oracle引入了名為CKPT的后臺進程,通常也叫作檢查點進程(checkpoint process)。這個進程與DBWR共同合作,從而確定這個起點。同時,這個起點也有一個專門的名字,叫做檢查點位置(checkpoint position)。
  oracle為了在檢查點的算法上更加的具有可擴展性(也就是為了能夠在巨大的buffer cache下依然有效工作),引入了檢查點隊列(checkpoint queue),該隊列上串起來的都是臟數(shù)據(jù)塊所對應(yīng)的buffer header。而DBWR每次寫臟數(shù)據(jù)塊時,也是從檢查點隊列上掃描臟數(shù)據(jù)塊,并將這些臟數(shù)據(jù)塊實際寫入數(shù)據(jù)文件的。當(dāng)寫完以后,DBWR會將這些已經(jīng)寫入數(shù)據(jù)文件的臟數(shù)據(jù)塊從檢查點隊列上摘下來。這樣即便是在巨大的buffer cache下工作,CKPT也能夠快速的確定哪些臟數(shù)據(jù)塊已經(jīng)被寫入了數(shù)據(jù)文件,而哪些還沒有寫入數(shù)據(jù)文件,顯然,只要在檢查點隊列上的數(shù)據(jù)塊都是還沒有寫入數(shù)據(jù)文件的臟數(shù)據(jù)塊。而且,為了更加有效的處理單實例和多實例(RAC)環(huán)境下的表空間的檢查點處理,比如將表空間設(shè)置為離線狀態(tài)或者為熱備份狀態(tài)等,oracle還專門引入了文件隊列(file queue)。

    文件隊列的原理與檢查點隊列是一樣的,只不過每個數(shù)據(jù)文件會有一個文件隊列,該數(shù)據(jù)文件所對應(yīng)的臟數(shù)據(jù)塊會被串在同一個文件隊列上;同時為了能夠盡量減少實例崩潰后恢復(fù)的時間,oracle還引入了增量檢查點(incremental checkpoint),從而增加了檢查點啟動的次數(shù)。假如每次檢查點啟動的間隔時間過長的話,再加上內(nèi)存很大,可能會使得恢復(fù)的時間過長。因為前一次檢查點啟動以后,標識出了這個起點。然后在第二次檢查點啟動的過程中,DBWR可能已經(jīng)將很多臟數(shù)據(jù)塊已經(jīng)寫入了數(shù)據(jù)文件,而假如在第二次檢查點啟動之前發(fā)生實例崩潰,導(dǎo)致在日志文件中,所標識的起點仍然是上一次檢查點啟動時所標識的,導(dǎo)致oracle不知道這個起點以后的很多重做條目所對應(yīng)的臟數(shù)據(jù)塊實際上已經(jīng)寫入了數(shù)據(jù)文件,從而使得oracle在實例恢復(fù)時再次重復(fù)的處理一遍,效率低下,浪費時間。
  上面說到了有關(guān)CKPT的兩個重要的概念:檢查點隊列(包括文件隊列)和增量檢查點。檢查點隊列在我們上面轉(zhuǎn)儲出來的buffer header里可以看到,就是類似ckptq: [65abceb4,63bec66c]和fileq: [65abcfbc,63becd10]的結(jié)構(gòu),記錄的同樣都是指向前一個buffer header和指向后一個buffer header的指針。這個隊列上面掛的也是臟數(shù)據(jù)塊對應(yīng)的buffer header鏈表,但是它與LRUW鏈表不同。檢查點隊列上的buffer header是按照數(shù)據(jù)塊第一次被修改的時間的先后順序來排列的。越早修改的數(shù)據(jù)塊的buffer header排在越前面,同時假如一個數(shù)據(jù)塊被修改了多次的話,在該鏈表上也只出現(xiàn)一次。而且,檢查點隊列上的buffer header還記錄了臟數(shù)據(jù)塊在第一次被修改時,所對應(yīng)的重做條目在重做日志文件中的地址,也就是RBA(Redo Block Address)。同樣在轉(zhuǎn)儲出來的buffer header中可以看到類似LRBA: [0xe9.229.0]的結(jié)構(gòu),這就是RBA,L表示Low,也就是第一次被修改的時候的RBA。但是注重,在檢查點隊列上的buffer header,并不表示一定會有一個對應(yīng)的RBA,比如控制文件重做(controlfile redo)就不會有相應(yīng)的RBA。對于沒有對應(yīng)RBA的buffer header來說,在檢查點隊列上始終處于最尾端,其優(yōu)先級永遠比有RBA的臟數(shù)據(jù)塊的buffer header要低。8i以前,每個working set都有一個檢查點隊列以及多個文件隊列(因為一個數(shù)據(jù)文件對應(yīng)一個文件隊列);而從8i開始,每個working set都有兩個檢查點隊列,每個檢查點都會由checkpoint queue latch來保護。   而增量檢查點是從8i開始出現(xiàn)的,是相對于8i之前的完全檢查點(complete checkpoint)而言的。完全檢查點啟動時,會標識出buffer cache中所有的臟數(shù)據(jù)塊,然后啟動DBWR進程將這些臟數(shù)據(jù)塊寫入數(shù)據(jù)文件。8i之前,日志切換的時候會觸發(fā)完全檢查點。而到了8i及以后,完全檢查點只有在兩種情況下才會被觸發(fā):1)發(fā)出命令:alter system checkpoint;2)除了shutdown abort以外的正常關(guān)閉數(shù)據(jù)庫。注重,這個時候,日志切換不會觸發(fā)完全檢查點,而是觸發(fā)增量檢查點。8i所引入的增量檢查點每隔三秒鐘或發(fā)生日志切換時啟動。它啟動時只做一件事情:找出當(dāng)前檢查點隊列上的第一個buffer header,并將該buffer header中所記錄的LRBA(這個LRBA也就是checkpoint position了)記錄到控制文件中去。假如是由日志切換所引起的增量檢查點,則還會將checkpoint position記錄到每個數(shù)據(jù)文件頭中。也就是說,假如這個時候發(fā)生實例崩潰,oracle在下次啟動時,就會到控制文件中找到這個checkpoint position作為在日志文件中的起點,然后從這個起點開始向后,依次取出每個重做條目進行處理。
上面所描述的概念,用一句話來概括,其實就是DBWR負責(zé)寫檢查點隊列上的臟數(shù)據(jù)塊,而CKPT負責(zé)記錄當(dāng)前檢查點隊列的第一個數(shù)據(jù)塊所對應(yīng)的的重做條目在日志文件中的地址。從這個意義上說,檢查點隊列比LRUW還要重要,LRUW主要就是區(qū)分出哪些數(shù)據(jù)塊是臟的,不可以被重用的。而到底應(yīng)該寫哪些臟數(shù)據(jù)塊,寫多少臟數(shù)據(jù)塊,則還是要到檢查點隊列上才能確定的。
我們用一個簡單的例子來描述這個過程。假設(shè)系統(tǒng)中發(fā)生了一系列的事務(wù),導(dǎo)致日志文件如下所示:
buffer cache深度分析之內(nèi)部治理機制(圖一)事務(wù)號 數(shù)據(jù)文件號 block號 行號 列 值 RBAbuffer cache深度分析之內(nèi)部治理機制(圖一)T1 8 25 10 1 10 101buffer cache深度分析之內(nèi)部治理機制(圖一)T1 7 623 12 2 a 102buffer cache深度分析之內(nèi)部治理機制(圖一)T3 8 80 56 3 b 103buffer cache深度分析之內(nèi)部治理機制(圖一)T3 9 98 124 7 e 104buffer cache深度分析之內(nèi)部治理機制(圖一)T5 7 623 13 3 abc 105buffer cache深度分析之內(nèi)部治理機制(圖一)Commit SCN# timestamp 106buffer cache深度分析之內(nèi)部治理機制(圖一)T123 8 876 322 10 89 107buffer cache深度分析之內(nèi)部治理機制(圖一)
這時,對應(yīng)的檢查點隊列則類似如下圖六所示。我們可以看到,T1事務(wù)最先發(fā)生,所以位于檢查點
  buffer cache深度分析之內(nèi)部治理機制(圖六)                                 圖六

   隊列的首端,而事務(wù)T123最后發(fā)生,所以位于靠近尾端的地方。同時,可以看到事務(wù)T1和T5都更新了7號數(shù)據(jù)文件的623號數(shù)據(jù)塊。而在檢查點隊列上只會記錄該數(shù)據(jù)塊的第一次被更新時的RBA,也就是事務(wù)T1對應(yīng)的RBA102,而事務(wù)T5對應(yīng)的RBA105并不會被記錄。 因為根本就不需要在檢查點隊列上記錄。當(dāng)DBWR寫數(shù)據(jù)塊的時候,在寫RBA102時,自然就把RBA105所修改的內(nèi)容寫入數(shù)據(jù)文件了。日志文件中所記錄的提交標記也不會體現(xiàn)在檢查點隊列上,因為提交本身只是一個標記而已,不會涉及到修改數(shù)據(jù)塊。

   這時,假設(shè)發(fā)生三秒鐘超時,于是增量檢查點啟動。增量檢查點會將檢查點隊列的第一個臟數(shù)據(jù)塊所對應(yīng)的RBA記錄到控制文件中去。在這里,也就是RBA101會作為checkpoint position記錄到控制文件中。
 然后,DBWR后臺進程被某種條件觸發(fā)而啟動。DBWR根據(jù)一系列參數(shù)及規(guī)則,計算出應(yīng)該寫的臟數(shù)據(jù)塊的數(shù)量,從而將RBA101到RBA107之間的這5個臟數(shù)據(jù)塊寫入數(shù)據(jù)文件,并在寫完以后將這5個臟數(shù)據(jù)塊從檢查點隊列上摘除,而留下了4個臟數(shù)據(jù)塊在檢查點隊列上。假如在寫這5個臟數(shù)據(jù)塊的過程中發(fā)生實例崩潰,則下次實例啟動時,oracle會從RBA101開始應(yīng)用日志文件中的重做條目。

  buffer cache深度分析之內(nèi)部治理機制(圖七)
                                   圖七

  而在9i以后,在DBWR寫完這5個臟數(shù)據(jù)塊以后,還會在日志文件中記錄所寫的臟數(shù)據(jù)塊的塊號。如下圖所示。這主要是為了在恢復(fù)時加快恢復(fù)的速度。
  buffer cache深度分析之內(nèi)部治理機制(圖八)
                                   圖八

  這時,又發(fā)生三秒鐘超時,于是增量檢查點啟動。這時它發(fā)現(xiàn)checkpoint position為RBA109,于是將RBA109寫入控制文件。假如接著發(fā)生實例崩潰,則oracle在下次啟動時,就會從RBA109開始應(yīng)用日志。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 炎陵县| 长白| 甘孜| 平果县| 白银市| 天长市| 吉隆县| 富宁县| 鄂尔多斯市| 鄱阳县| 长宁县| 阿瓦提县| 五莲县| 雷州市| 临高县| 临潭县| 北票市| 杭州市| 贺兰县| 岳池县| 宜川县| 高要市| 蒲江县| 平罗县| 洞头县| 荃湾区| 普格县| 文登市| 化隆| 大姚县| 漳平市| 大名县| 宝应县| 临沭县| 湘乡市| 洪泽县| 赤峰市| 开江县| 深泽县| 兴安盟| 芦山县|