1. 程式人生 > >log buffer及日誌管理深入分析及效能調整(一)

log buffer及日誌管理深入分析及效能調整(一)

1. log buffer的概念

1.1 log buffer概述

資料庫在執行過程中,不可避免的要遇到各種能夠導致資料塊庫損壞的情況。比如突然斷電、oracle或者作業系統的程式bug導致資料庫內部邏輯結構損壞、磁碟介質損壞等,都有可能造成資料庫崩潰,從而導致資料丟失的現象發生。

為了避免,或者說為了修復這些狀況所導致的資料丟失現象,oracle引入了日誌緩衝區和日誌檔案的概念。所謂日誌,就是將所有資料庫中所有改變資料塊的操作,都原原本本的記錄下來。這些改變資料塊的操作不僅包括對資料表的DML或者對資料字典的DDL,還包括對索引的改變、對回滾段資料塊的改變、對臨時表空間的臨時段的改變等。只有將資料庫中所有的變化都記錄下來,當發生資料庫損壞時,才能夠從損壞時的那一點開始,將之後資料庫中的變化重新運用一遍,從而達到恢復資料庫的目的。

既然是要記錄,那就必然引出一個問題,就是如何記錄這些變化?比較容易想到的有兩種方式。

第一種是使用邏輯的記錄方式,也就是用描述性的語句來記錄整個變化過程。比如對於某個update更新操作來說來說,可以記錄為兩條語句:delete舊值以及insert新值。這種方式的優點是非常節省空間,因為對每個操作,只需要記錄幾條邏輯上的語句即可。但是缺點也很明顯,就是一旦需要進行恢復,就會非常消耗資源。設想一下,某個update操作更新了非常多的資料塊,由於buffer cache記憶體有限,很多髒資料塊都已經寫入了資料檔案。但就在更新快結束時,突然發生斷電,所做的更新丟失。那麼重新啟動例項時,oracle需要應用日誌檔案裡的記錄,於是重新發出delete舊值以及insert新值的語句。這個過程需要重新查詢資料檔案中符合條件的資料塊,然後再挑出來進行更新。這個過程將非常消耗時間,而且會佔用大量的buffer cache。

第二種方式是使用物理的記錄方式,也就是將每個資料塊改變前的映象和改變後的映象都記錄下來。這種方式優點就是恢復起來速度非常快,直接根據日誌檔案裡所記錄的資料塊地址和內容更新資料檔案中對應的資料塊。但是缺點也很明顯,就是非常佔用磁碟空間。

而oracle在記錄日誌的方式上,採用了邏輯和物理相結合的方式。也就是說,oracle針對每個資料塊,記錄了插入某個值或者刪除某個值的描述語句。假如某個update更新了100個數據塊,則oracle會針對每個資料塊記錄一對delete舊值和insert新值的語句,共有100對這樣的描述語句。通過這種方式,oracle獲得了物理記錄方式的快速恢復的優點,同時又獲得了邏輯記錄方式的節省空間的優點。

為了臨時存放所產生的日誌資訊,oracle在SGA中開闢了一塊記憶體區域。這塊區域就叫做日誌緩衝區(log buffer),當滿足一定條件以後,oracle會使用名為LGWR的後臺程序將log buffer中的日誌資訊寫入聯機日誌檔案裡。

可以使用初始化引數log_buffer來設定日誌緩衝區的大小,單位是位元組。日誌緩衝區會進一步細分為多個塊,每個塊的尺寸與作業系統的一個塊的尺寸相同,基本都是512位元組。我們可以用如下方式來獲得日誌緩衝區的塊尺寸。

SQL> select distinct lebsz asredo_block_size from x$kccle;

REDO_BLOCK_SIZE

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

512

日誌緩衝區只是日誌資訊臨時存放的區域,這塊區域是有限的,而且其中的每個塊都是能夠迴圈使用的。這也就說明,日誌緩衝區中的內容必須要寫入磁碟上的檔案裡,才能永久保留下來,才能在資料庫崩潰時能夠用來進行恢復。這個檔案就叫做聯機日誌檔案(redo日誌)。在每個日誌緩衝區中的日誌塊被重用之前,其內容必然已經被寫入了磁碟上的聯機日誌檔案中。

聯機日誌檔案就是日誌緩衝區的完全拷貝,組成日誌檔案的每個日誌塊的內容都來自於日誌緩衝區的日誌塊。每個日誌緩衝區中的日誌塊都對應到日誌檔案中的一個日誌塊。日誌緩衝區中的日誌塊按照發生的先後順序,放入聯機日誌檔案。由於日誌檔案在故障恢復中的重要性,建議至少使用兩個日誌檔案組成一個日誌檔案組。同一個日誌檔案組中的日誌檔案內容一摸一樣,因為日誌緩衝區中的日誌塊同時會寫入日誌檔案組中的每個日誌檔案中。每個資料庫都必須至少擁有兩個日誌檔案組。這是由於只要資料庫一天不停止執行,就會不斷產生日誌資訊,就會不斷寫入聯機日誌檔案,聯機日誌檔案總會有寫滿的時候。我們不可能讓聯機日誌檔案無限大,也不可能放無限多的聯機日誌檔案,所以聯機日誌檔案必須是迴圈使用的,在若干個日誌檔案中輪流的進行寫入。一個日誌檔案寫滿以後轉換到另外一個日誌檔案繼續寫的過程叫做日誌切換(log switch)。

當一個聯機日誌檔案寫滿時,可以選擇將其歸檔為離線日誌檔案,通常叫做歸檔日誌檔案。歸檔也就是拷貝,歸檔的過程也就是將寫滿的聯機日誌檔案拷貝到預先指定的目錄的過程。只有當一個聯機日誌檔案完成歸檔以後,該聯機日誌檔案才能夠被再次迴圈使用。強烈建議在生產庫中選擇這種歸檔方式,只有在測試環境中可以不選擇這種歸檔方式。

可以說,日誌緩衝區和日誌檔案存在的唯一目的就是為了保證被修改的資料不會被丟失。反過來說,也就是為了能夠在資料庫崩潰的時候,可以用來將資料庫恢復到崩潰的那個時間點上。這也就是說,只有將被修改的資料塊的日誌資訊寫入了聯機日誌檔案以後,該被修改的資料塊才可以說是安全的。如果日誌資訊在沒有被寫入日誌檔案時發生例項崩潰,這時對資料的修改仍將丟失。由此我們可以看出,將日誌緩衝區中的日誌資訊寫入日誌檔案是一個多麼重要的過程,這個過程是由一個名為LGWR的後臺程序完成的。LGWR承擔了維護系統資料完整性的任務,它保證了資料在任何情況下都不會丟失。

觸發LGWR程序將日誌緩衝區中的日誌資訊寫入聯機日誌檔案條件包括以下幾種:

前臺程序觸發,包括兩種情況。最顯而易見的一種情況就是使用者發出commit或rollback語句進行提交時,需要觸發LGWR將記憶體裡的日誌資訊寫入聯機日誌檔案,因為提交的資料必須被保護而不被丟失;另外一種情況就是在日誌緩衝區中找不到足夠的記憶體來放日誌資訊時,也會觸發LGWR程序將一些日誌資訊寫入聯機日誌檔案以後,從而釋放一些空間出來。

每隔三秒鐘,LGWR啟動一次。

在DBWR啟動時,如果發現髒資料塊所對應的重做條目還沒有寫入聯機日誌檔案,則DBWR觸發LGWR程序並等待LRWR寫完以後才會繼續。

日誌資訊的數量達到整個日誌緩衝區的1/3時,觸發LGWR。

日誌資訊的數量達到1MB時,觸發LGWR。

發生日誌切換時觸發LGWR。

1.2 log buffer的記憶體結構

我們已經知道,日誌緩衝區用來存放事務對資料塊的改變的日誌資訊。那麼這裡的日誌資訊到底包含哪些內容,是由哪些結構組成的呢?

oracle記錄資料庫變化(也就是記錄日誌資訊)的最小單位是改動向量(change vector。改動向量用來描述對資料庫中任何單個數據塊所做的一次改動。改動向量的內容包括:被改動的資料塊的版本號、事務操作程式碼、被改動的資料塊的地址等。這裡的版本號非常重要,它能夠幫助資料塊始終能夠體現當前最新的狀態。oracle在建立改動向量時,會從資料塊中拷貝其版本號。而當恢復期間,oracle讀取改動向量並將改動應用於相應的資料塊以後,被恢復的資料塊的版本號加1。這裡的資料塊可以屬於表、也可以資料索引、也可以屬於回滾段。但是對於臨時表空間裡的臨時段,不會生成改動向量。

當多個改動向量按照先後順序組合在一起,從而完成對資料庫的一次改動時,oracle稱這組改動向量為重做記錄(redo record。重做記錄用來描述對資料庫的一個原子改動。所謂原子改動,就是說,當應用改動中的改動向量時,要麼全部成功,要麼全部失敗,不存在部分成功部分失敗的情況可以理解為一個事務操作)。重做記錄能夠幫助整個資料庫體現當前最新的狀態。

一個事務至少產生一個重做記錄,也可能產生多個重做記錄。而oracle在應用日誌記錄進行恢復的過程中,以事務作為恢復的最小單位。要麼恢復整個事務,要麼回滾整個事務。也就是說,要麼運用事務中的重做記錄裡的所有改動向量,要麼一個改動向量都不運用。

因此,日誌緩衝區就是許多重做記錄按照發生的先後順序組成的。同時,日誌檔案也就是由許多重做記錄按照先後順序排列在一起而組成的檔案。

我們舉個例項來說明重做記錄和改動向量產生的過程。比如我們發出如下更新語句(假設表redo_test的name列上沒有建立索引):

SQL> select * from redo_test;

      ID NAME

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

       1 abc

       2 abc

SQL> update redo_test set name='cdf'where id=1;

該語句發出以後,會產生一個重做記錄,用來描述對錶的資料塊的修改。包括下面三個改動向量:

對回滾段事務表的改動,這發生在回滾段段頭。事務表中包含被修改的資料塊的地址、該事務的狀態(commit或active)、以及存有該事務所使用的回滾段的地址。如果事務表被修改,就會產生針對於它的改動向量。

對回滾段資料塊的改動。將修改前的舊值(abc)存放到回滾段的資料塊裡。這時回滾段發生改變,於是產生改動向量。

對redo_test表的資料塊所做的改動。將修改後的新值(cdf)存放到表的資料塊裡。這時資料塊發生改變,於是產生改動向量。

從這個例項可以知道,對於這個update事務,重做記錄中會有三個改動向量。當然可能有其他情況會產生新的重做記錄,比如修改的列如果有索引,則必須修改索引。這時就會產生第二個重做記錄,用來描述對索引資料塊的修改。這時候的重做記錄還是和第一個重做記錄一樣,包含多個改動向量。此外,在事務完成之後執行commit或rollback語句時,就會產生第三個重做記錄。該重做記錄只有一個改動向量,用來記錄對回滾段事務表的更改,因為commit或rollback時,需要更新事務表裡記錄的該事務的狀態。

1.3轉儲log buffer

oracle對很多記憶體結構都提供了轉儲到平面檔案的功能,但是並沒有直接提供轉儲日誌緩衝區的功能。但是提供了轉儲日誌檔案的功能。我們前面已經知道,日誌檔案的內容就是日誌緩衝區的完全拷貝,因此,轉儲日誌檔案就等於轉儲了日誌緩衝區。

轉儲日誌檔案的命令為:

alter system dump logfile 'logfilename';

我們的實驗過程如下。首先找到當前狀態為CURRENT的日誌檔案是哪一個,以及它的全路徑。

SQL> select group#,status from v$log;

   GROUP# STATUS

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

        1INACTIVE

        2INACTIVE

        3CURRENT

SQL> select member from v$logfile wheregroup#=3;

MEMBER

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

/oracle/oradata/ora10g/redo03.log

然後,找到我們的redo_test表的object id,並進行更新操作。

SQL> desc redo_test

 Name                                     Null?   Type

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

 ID                                                NUMBER

 NAME                                              VARCHAR2(10)

SQL> select object_id from user_objectswhere object_name='REDO_TEST';

 OBJECT_ID

----------

    51367

SQL> select * from redo_test;

       IDNAME

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

        1abc

        2abc

SQL> update redo_test set name='cdf'where id=1;

SQL> commit;

             最後,我們轉儲出當前使用的日誌檔案。

SQL> alter system dump logfile'/oracle/oradata/ora10g/redo03.log';

開啟生成的跟蹤檔案,從中找到含有51367(表redo_test的object id)的所有相關記錄,如下圖所示。


我們可以看到,第一行明確說明了,下面的內容都屬於同一個重做記錄(REDO RECORD)。

往下可以看到的CHANGE #的字樣,這裡的CHANGE #就表示改動向量(CHANGE VECTOR),我們可以看到一共生成了四個改動向量(第3、15、18、21行)。正像我們前面所描述的那樣,第一個是對回滾段事務表的改動(CHANGE #2),第二個是對回滾段資料塊的改動(CHANGE #4),第三個是對redo_test表的資料塊的改動(CHANGE #1),第四個是提交時對回滾段事務表的改動(CHANGE #3)。注意,這裡的CHANGE #後面的數值並不表示發生順序,只不過是一個標識而已,發生的先後順序要以SCN號為準,SCN號越小的表示越早發生。另外一個需要注意的是,在這個例子裡,第四個提交時對回滾段事務表的改動(CHANGE #3)的改動向量並沒有單獨成為一個重做記錄,這是因為我們發出update語句以後立即提交了。如果發出update語句以後,不立即提交,而是等待一段時間,然後再提交。這時轉儲出來的日誌檔案會顯示出兩個重做記錄,最後的提交時對回滾段事務表的改動的改動向量會單獨歸到一個重做記錄裡去。後面會說明原因。

第8行的slot: 0表示被更新的記錄位於資料塊中的第一行。這與第35行的slot是一樣的。如果我們發出“update redo_test set name='cdf' where id=2”時,就會發現這時slot為1。

第9行的size: 0表示修改後的值的長度減去修改前的值的長度的結果。而第36行的size表示修改前的值的長度減去修改後的值的長度的結果。由於我們這裡修改前後的值的長度都是3,所以差額為0。如果我們發出“update redo_test set name='cdfg' where id=1”時,就會發現第9行的值為1,第36行的值為-1。

第11行的bdba表示redo_test表的資料塊的地址,其實就是第3行的DBA所顯示的地址。而hdba則表示redo_test表的segment header的地址。與第38行的值是相同的。第14行的col 1表示被更新的是表的第二列(第一列是col 0)。後面的[3]表示該列的值的長度,單位是位元組。它們與第41行的內容是相同的。再後面的63 64 66則表示col 1列被更新後的值。我們來轉換一下,如下所示。可以看到這正是我們更新後的“cdf”值。同樣我們可以看到第41行的61 62 63,表示更新前的值,也就是“abc”。

SQL> selectchr(to_number(63,'xx')),chr(to_number(64,'xx')),

 2 chr(to_number(66,'xx')) fromdual;

C C C

- - -

c d f

第15行的DBA:0x00800009表示回滾段事務表的地址,這與第18行的DBA是一樣的。由於回滾段事務表位於回滾段段頭裡,所以這也就是回滾段段頭的地址。008表示檔案號乘以4,00009表示資料塊地址。我們轉儲出該段頭來看看裡面放了些什麼。

SQL> select to_number('008','xxx')/4file#,

 2 to_number('00009','xxxxx')block# from dual;

    FILE#    BLOCK#

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

        2         9

SQL> alter system dump datafile 2 block9;

我們開啟所生成的跟蹤檔案,可以看到存放的正是回滾段事務表的資訊。如下圖二所示。我們可以看到粗體顯示的正是我們發出的update語句在事務表中所登記的條目,state為9表示事務已經提交。


第17行的uba:0x0080085a.0075.13表示存放redo_test被更新前舊值的回滾段資料塊所在的地址,這與第20行的uba是一樣的。0080085a表示回滾段資料塊所在的地址,0075表示順序號,13表示在回滾對映中的最後一個條目。我們只需要關心0080085a即可,其組成部分和前面所介紹過的是一樣的,008表示檔案號乘以4,0085a表示回滾段資料塊的地址。

SQL> select to_number('008','xxx')/4 asfile#,

 2 to_number('0085a','xxxxx') asblock# from dual;

    FILE#    BLOCK#

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

        2      2138

SQL> alter system dump datafile 2 block2138;

然後,開啟生成的跟蹤檔案,找到含有51367(表redo_test的object id)的相關記錄,如下圖三所示。


  圖三

從上圖中我們可以看到熟悉的bdba和hdba。以及最後一行的61 6263,這就是更新前的舊值。同時我們還可以注意到KDO Op code: 21,這表示事務所進行的操作的型別程式碼。因為我是在10g環境下測試的,所以這裡顯示了程式碼21,就表示進行的是update操作。如果在10g以前,會顯示URP字樣。