天天看點

Oracle程序詳解--DBWn,CKPT,LGWR

DBWn

我們DBWn程序負責将髒資料塊寫入磁盤。它是一個非常重要的程序,随着記憶體的不斷增加,一個DBWn程序可能不夠用了。是以從Oracle 8i起,我們可以為系統配置多個DBWn程序。初始化參數db_writer_processe決定了啟動多少個DBWn程序。每個DBWn程序都會配置設定一個cache buffers lru chain latch。

DBWn作為一個背景程序,隻有在某些條件滿足了才會觸發。這些條件包括:

當程序在LRU連結清單掃描以查找可以覆寫的buffer header時,如果已經掃描的buffer header的數量到達一定的限度時,觸發DBWn程序;

如果髒資料塊的總數超過一定限度,也将觸發DBWn程序;

發生檢查點(包括增量檢查點(incremental checkpoint)和完全檢查點(complete checkpoint))時觸發DBWn;

每隔三秒鐘啟動一次DBWn;

DBWn、CKPT、LGWR程序之間的合作

将記憶體資料塊寫入資料檔案實在是一個相當複雜的過程,在這個過程中,首先要保證安全。所謂安全,就是在寫的過程中,一旦發生執行個體崩潰,要有一套完整的機制能夠保證使用者已經送出的資料不會丢失;其次,在保證安全的基礎上,要盡可能地提高效率。衆所周知,I/O操作是最昂貴的操作,是以應該盡可能地将髒資料塊收集到一定程度以後,再批量寫入磁盤中。

直覺上最簡單的解決方法就是,每當使用者送出的時候就将所改變的記憶體資料塊交給DBWn,由其寫入資料檔案。這樣的話,一定能夠保證送出的資料不會丢失。但是這種方式效率最為低下,在高并發環境中,一定會引起I/O方面的争用。Oracle當然不會采用這種沒有伸縮性的方式。Oracle引入了CKPT和LGWR這兩個背景程序,這兩個程序與DBWn程序互相合作,提供了既安全又高效的寫髒資料塊的解決方法。

使用者程序每次修改記憶體資料塊時,都會在日志緩沖區(log buffer)中構造一個相應的重做條目(redo entry),該重做條目描述了被修改的資料塊在修改之前和修改之後的值。而LGWR程序則負責将這些重做條目寫入聯機日志檔案。隻要重做條目進入了聯機日志檔案,那麼資料的安全就有保障了,否則這些資料都是有安全隐患的。LGWR是一個必須和前台使用者程序通信的程序。LGWR承擔了維護系統資料完整性的任務,它保證了資料在任何情況下都不會丢失。

假如DBWR在寫髒資料塊的過程中,突然發生執行個體崩潰時,該怎麼辦?我們知道,使用者送出時,Oracle是不一定會把送出的資料塊寫入資料檔案的。那麼執行個體崩潰時,必然會有一些已經送出但是還沒有被寫入資料檔案的記憶體資料塊丢失了。當執行個體再次啟動時,Oracle需要利用日志檔案中記錄的重做條目在buffer cache中重新構造出被丢失的資料塊,進而完成前滾和復原的工作,并将丢失的資料塊找回來。于是這裡就存在一個問題,就是Oracle在日志檔案中找重做條目時,到底應該找哪些重做條目?換句話說,應該在日志檔案中從哪個起點開始往後應用重做條目?注意,這裡所指的日志檔案可能不止一個日志檔案。

因為需要預防随時可能的執行個體崩潰現象,是以Oracle在資料庫的正常運作過程中,會不斷地定位這個起點,以便在不可預期的執行個體崩潰中能夠最有效地保護并恢複資料。同時,這個起點的選擇非常有講究。首先,這個起點不能太靠近日志檔案的頭部,太靠近日志檔案頭部意味着要處理很多的重做條目,這樣會導緻執行個體再次啟動時所進行恢複的時間太長;其次,這個起點也不能太靠近日志檔案的尾部,太靠近日志檔案的尾部說明隻有很少的髒資料塊沒有被寫入資料檔案,也就是說前面已經有很多髒資料塊被寫入了資料檔案,那也就意味着隻有在DBWn程序很頻繁地寫資料檔案情況下,才能使得buffer cache中所殘留的髒資料塊的數量很少。但很明顯,DBWn寫得越頻繁,那麼所占用寫資料檔案的I/O就越嚴重,那麼留給其他操作(比如讀取buffer cache中不存在的資料塊等)的I/O資源就越少。這顯然也是不合理的。

從這裡也可以看出,這個起點實際上說明了,在日志檔案中位于這個起點之前的重做條目所對應的在buffer cache中的髒資料塊已經被寫入了資料檔案,進而在執行個體崩潰以後的恢複中不需要去考慮。而這個起點以後的重做條目所對應的髒資料塊實際還沒有被寫入資料檔案,如果在執行個體崩潰以後的恢複中,需要從這個起點開始往後,依次取出日志檔案中的重做條目進行恢複。考慮到目前的記憶體容量越來越大,buffer cache也越來越大,buffer cache中包含幾百萬個記憶體資料塊也是很正常的現象的前提下,如何才能最有效的來定位這個起點呢?

為了能夠确定這個最佳的起點,Oracle引入了名為CKPT的背景程序,通常也叫作檢查點程序(checkpoint process)。這個程序與DBWn共同合作,進而确定這個起點。同時,這個起點也有一個專門的名字,叫做檢查點位置(checkpoint position,該檢查點位置記錄在控制檔案裡)。Oracle為了在檢查點的算法上更加的具有可擴充性(也就是為了能夠在巨大的buffer cache下依然有效工作),引入了檢查點隊列(checkpoint queue),該隊列上串起來的都是髒資料塊所對應的buffer header。而每次DBWn寫髒資料塊時,也是從檢查點隊列上掃描髒資料塊,并将這些髒資料塊實際寫入資料檔案的。當寫完以後,DBWn會将這些已經寫入資料檔案的髒資料塊從檢查點隊列上摘下來。這樣即便是在巨大的buffer cache下工作,CKPT也能夠快速的确定哪些髒資料塊已經被寫入了資料檔案,而哪些還沒有寫入資料檔案,顯然,隻要在檢查點隊列上的資料塊都是還沒有寫入資料檔案的髒資料塊。而且,為了更加有效的處理單執行個體和多執行個體(RAC)環境下的表空間的檢查點處理,比如将表空間設定為離線狀态或者為熱備份狀态等,Oracle還專門引入了檔案隊列(file queue)。檔案隊列的原理與檢查點隊列是一樣的,隻不過每個資料檔案會有一個檔案隊列,該資料檔案所對應的髒資料塊會被串在同一個檔案隊列上;同時為了能夠盡量減少執行個體崩潰後恢複的時間,Oracle還引入了增量檢查點(incremental checkpoint),進而增加了檢查點啟動的次數。如果每次檢查點啟動的間隔時間過長的話,再加上記憶體很大,可能會使得恢複的時間過長。因為前一次檢查點啟動以後,辨別出了這個起點。然後在第二次檢查點啟動之前,DBWn可能已經将很多髒資料塊已經寫入了資料檔案,而假如在第二次檢查點啟動之前發生執行個體崩潰,導緻在日志檔案中,所辨別的起點仍然是上一次檢查點啟動時所辨別的,導緻Oracle不知道這個起點以後的很多重做條目所對應的髒資料塊實際上已經寫入了資料檔案,進而使得Oracle在執行個體恢複時重複地處理一遍,效率低下,浪費時間。

上面說到了有關CKPT的兩個重要的概念:檢查點隊列(包括檔案隊列)和增量檢查點。檢查點隊列上的buffer header是按照資料塊第一次被修改的時間的先後順序來排列的。越早修改的資料塊的buffer header排在越前面,同時如果一個資料塊被修改了多次的話,在該連結清單上也隻出現一次。而且,檢查點隊列上的buffer header還記錄了髒資料塊在第一次被修改時,所對應的重做條目在重做日志檔案中的位址,也就是LRBA(Low Redo Block Address),Low表示第一次修改時對應的RBA。每個檢查點都會由checkpoint queue latch來保護。

而增量檢查點是從Oracle 8i開始出現的,是相對于Oracle 8i之前的完全檢查點(complete checkpoint)而言的。完全檢查點啟動時,會辨別出buffer cache中所有的髒資料塊,然後以最高優先級啟動DBWn程序将這些髒資料塊寫入資料檔案。Oracle 8i之前,日志切換的時候會觸發完全檢查點。而到了Oracle 8i及以後,完全檢查點隻有在兩種情況下才會被觸發:

發出alter system checkpoint指令;

除了shutdown abort以外的正常關閉資料庫。

注意,這個時候,日志切換不會觸發完全檢查點,而是觸發增量檢查點。Oracle 8i所引入的增量檢查點每隔三秒鐘或發生日志切換時啟動。它啟動時隻做一件事情:找出目前檢查點隊列上的第一個buffer header,并将該buffer header中所記錄的LRBA(這個LRBA也就是checkpoint position)記錄到控制檔案中去。如果是由日志切換所引起的增量檢查點,則還會将checkpoint position記錄到每個資料檔案頭中。也就是說,如果這個時候發生執行個體崩潰,Oracle在下次啟動時,就會到控制檔案中找到這個checkpoint position作為日志檔案的起點,然後從這個起點開始向後,依次取出每個重做條目進行處理。

上面所描述的概念,用一句話來概括,其實就是DBWn負責寫檢查點隊列上的髒資料塊,而CKPT負責記錄目前檢查點隊列的第一個資料塊所對應的的重做條目在日志檔案中的位址。而到底應該寫哪些髒資料塊,寫多少髒資料塊,則要到檢查點隊列上才能确定的。

我們用一個簡單的例子來描述這個過程。假設系統中發生了一系列的事務,導緻日志檔案如下所示:

事務号  資料檔案号  block号  行号  列  值  RBA

T1         8             25         10    1  10  101

T1         7             623       12    2   a   102

T3         8              80         56    3   b   103

T3         9              98         124  7   e   104

T5          7              623       13    3   abc 105

Commit SCN#  timestamp       106

T123     8               876       322  10  89  107

Oracle程式詳解--DBWn,CKPT,LGWR

這時,對應的檢查點隊列則類似上圖所示。101、102 … 107表示LRBA,而8/25、7/623 … 8/876表示資料塊号/檔案号。

我們可以看到,T1事務最先發生,是以位于檢查點隊列的首端,而事務T123最後發生,是以位于靠近尾端的地方。同時,可以看到事務T1和T5都更新了7号資料檔案的623号資料塊。而在檢查點隊列上隻會記錄該資料塊的第一次被更新時的RBA,也就是事務T1對應的RBA102,而事務T5對應的RBA105并不會被記錄。當DBWn寫資料塊的時候,在寫RBA102時,自然就把RBA105所修改的内容寫入資料檔案了。日志檔案中所記錄的送出标記也不會展現在檢查點隊列上,因為送出本身隻是一個标記而已,不會涉及修改資料塊。

這時,假設發生三秒鐘逾時,于是增量檢查點啟動。增量檢查點會将檢查點隊列的第一個髒資料塊所對應的LRBA記錄到控制檔案中去。在這裡,也就是RBA101會作為checkpoint position記錄到控制檔案中。

然後,啟動DBWn背景程序。DBWn根據一系列參數及規則,計算出應該寫的髒資料塊的數量,假設将RBA101到RBA107之間的這5個髒資料塊寫入資料檔案,并在寫完以後将這5個髒資料塊從檢查點隊列上摘除,而留下了4個髒資料塊在檢查點隊列上,如下圖所示。

Oracle程式詳解--DBWn,CKPT,LGWR

如果在寫這5個髒資料塊的過程中發生執行個體崩潰,則下次執行個體啟動時,Oracle會從RBA101開始應用日志檔案中的重做條目。

Oracle程式詳解--DBWn,CKPT,LGWR

從檢查點對列上摘除髒塊

而在Oracle 9i以後,在DBWR寫完這5個髒資料塊以後,還會在日志檔案中記錄所寫的髒資料塊的塊号。上圖所示。這主要是為了在恢複時加快恢複的速度。

這時,假設又發生三秒鐘逾時,于是增量檢查點啟動。這時它發現checkpoint position為RBA109,于是将RBA109寫入控制檔案。如果接着發生執行個體崩潰,則Oracle在下次啟動時,就會從RBA109開始往下應用日志。

參考至:《教你成為10g OCP》韓思捷著

如有錯誤,歡迎指正

郵箱:[email protected]