(轉)老白的理解REDO LOG

xz43發表於2011-02-19

REDO LOGOracle為確保已經提交的事務不會丟失而建立的一個機制。實際上REDO LOG的存在是為兩種場景準備的,一種我們稱之為例項恢復(INSTANCE RECOVERY),一種我們稱之為介質恢復(MEDIA RECOVERY)。例項恢復的目的是在資料庫發生故障時,確保BUFFER CACHE中的資料不會丟失,不會造成資料庫的不一致。介質恢復的目的是當資料檔案發生故障時,能夠恢復資料。雖然這兩種恢復使用的機制類似的,但是這兩種恢復也有著十分本質的不同,這一點也是很多DBA經常會混淆的。

REDO LOG的資料是按照THREAD來組織的,對於單例項系統來說,只有一個THREAD,對於RAC系統來說,可能存在多個THREAD,每個資料庫例項擁有一組獨立的REDO LOG檔案,擁有獨立的LOG BUFFER,某個例項的變化會被獨立的記錄到一個THREADREDO LOG檔案中。

對於介質恢復和例項恢復來說,第一個步驟都是透過REDO LOG的資訊進行前滾,在做前滾的時候,透過REDO LOG檔案裡記錄的資料庫變化向量(稍後我們會詳細的介紹資料庫變化向量CV),根據SCN的比對,提交到相關的資料檔案上,從而使資料檔案的狀態向前滾動。大家要注意的是,UNDO表空間的變化也被記錄到REDO LOG裡了,因此UNDO表空間相關的資料檔案也會被前滾。當前滾到最後一個可用的REDO LOG或者歸檔日誌的時候,所有的資料庫恢復層面的工作就全部完成了。這個時候,資料庫包含了所有的被記錄的變化,這些變化中有些是已經提交,有些是尚未提交的。在最新狀態的UNDO表空間中,我們也可以看到一些尚未提交的事務。

因此資料庫下一步需要做的事情是事務層面的處理,回滾那些尚未提交的事務,以確保資料庫的一致性。

對於單例項的系統,例項恢復一般是在資料庫例項異常故障後資料庫重啟時進行,當資料庫執行了SHUTDOWN ABORT或者由於作業系統、主機等原因當機重啟後,在ALTER DATABASE OPEN的時候,就會自動做例項恢復。而在RAC環境中,如果某個例項宕了,或者的例項將會接管,替宕掉的例項做例項恢復。除非是所有的例項都宕了,這樣的話,第一個執行ALTER DATABASE OPEN的例項將會做例項恢復。這也是REDO LOG是例項私有的元件,但是REDO LOG檔案必須存放在共享儲存上的原因。

Oracle資料庫的CACHE機制是以效能為導向的,CACHE機制應該最大限度的提高資料庫的效能,因此CACHE被寫入資料檔案總是儘可能的推遲。這種機制大大提高了資料庫的效能,但是當例項出現故障時,可能出現一些問題。

首先是在例項故障時,可能某些事務對資料檔案的修改並沒有完全寫入磁碟,可能磁碟檔案中丟失了某些已經提交事務對資料檔案的修改資訊。其次是可能某些還沒有提交的事務對資料檔案的修改已經被寫入磁碟檔案了。也有可能某個原子變更的部分資料已經被寫入檔案,而部分資料還沒有被寫入磁碟檔案。例項恢復就是要透過ONLINE REDO LOG檔案中記錄的資訊,自動的完成上述資料的修復工作。這個過程是完全自動的,不需要人工干預。

在這個機制裡,有兩個問題需要解決,第一個是如何確保已經提交的事務不會丟失,第二個是如何在資料庫效能和例項恢復所需要的時間上做出平衡,既確保資料庫效能不會下降,又保證例項恢復的快速。

解決第一個問題比較簡單,Oracle有一個機制,叫做Log-Force-at-Commit,就是說,在事務提交的時候,和這個事務相關的REDO LOG資料,包括COMMIT記錄,都必須從LOG BUFFER中寫入REDO LOG檔案,此時事務提交成功的訊號才能傳送給使用者程式。透過這個機制,可以確保哪怕這個已經提交的事務中的部分BUFFER CACHE還沒有被寫入資料檔案,就發生了例項故障,在做例項恢復的時候,也可以透過REDO LOG的資訊,將不一致的資料前滾。

解決第二個問題,oracle是透過checkpoint機制來實現的。Oracle資料庫中,對BUFFER CAHCE的修改操作是前臺程式完成的,但是前臺程式只負責將資料塊從資料檔案中讀到BUFFER CACHE中,不負責BUFFER CACHE寫入資料檔案。BUFFER CACHE寫入資料檔案的操作是由後臺程式DBWR來完成的。DBWR可以根據系統的負載情況以及資料塊是否被其他程式使用來將一部分資料塊回寫到資料檔案中。這種機制下,某個資料塊被寫回檔案的時間可能具有一定的隨機性的,有些先修改的資料塊可能比較晚才被寫入資料檔案。而CHECKPOINT機制就是對這個機制的一個有效的補充,CHECKPOINT發生的時候,CKPT程式會要求DBWR程式將某個SCN以前的所有被修改的塊都被寫回資料檔案。這樣一旦這次CHECKPOINT完成後,這個SCN前的所有資料變更都已經存檔,如果之後發生了例項故障,那麼做例項恢復的時候,只需要從這次CHECKPOINT已經完成後的變化量開始就行了,CHECKPOINT之前的變化就不需要再去考慮了。

到目前為止,我們瞭解了例項恢復機制的一些基本的原理,我們也可以大體上理解REDO LOG的工作機制了。不過我想我們還需要再更加深入一些。瞭解一些更為深入的內幕。實際上透過上面老白的介紹,大家也許已經覺得對例項恢復瞭解的很透徹了,而實際上,有很多問題我們還沒有解決。有些愛動腦筋的讀者可能要問了,有沒有可能資料檔案中的變化已經寫盤,但是REDO LOG資訊還在LOG BUFFER中,沒有寫入REDO LOG呢,這種情況如何恢復呢?

這裡我們又要引入一個名詞:Write-Ahead-Log,就是日誌寫入優先。日誌寫入優先包含兩方面的演算法,第一個方面是,當某個BUFFER CACHE的修改的變化向量還沒有寫入REDO LOG檔案之前,這個修改後的BUFFER CACHE的資料不允許被寫入資料檔案,這樣就確保了再資料檔案中不可能包含未在REDO LOG檔案中記錄的變化;第二個方面是,當對某個資料的UNDO資訊的變化向量沒有被寫入REDO LOG之前,這個BUFFER CACHE的修改不能被寫入資料檔案。

介質恢復和例項恢復的機制是類似的,所不同的是,介質恢復是當儲存的資料檔案出現故障的時候進行的,介質恢復無法自動進行,必須手工執行recover Database或者recover datafile命令來實施。一般來說,介質恢復是從一個恢復的資料檔案為起點進行恢復,因此在做介質恢復的時候,需要使用歸檔日誌。

 

下面我們要了解一些REDO LOG底層的概念,只有將這幾個概念搞明白了,我們才能更加深入的瞭解REDO LOG,以及瞭解REDO LOG相關的管理和最佳化的要點。首先我們要了解的就是變化向量(CHANGE VECTORCV),變化向量是組成REDO資訊的基礎,一個變化向量描述了對一個獨立的資料塊的一個獨立的修改操作。這裡面我們要注意的是,對於CV的定義裡包含了兩層含義,一個CV只針對一個資料塊的變更,一個CV只包含一個變化。每個CV都包含了對檔案的修改,因此在每個CV中都有一個OPCODE,指出修改的型別。不同OPCODECV,其組成是不同的,OPCODE的取值範圍包括:

    Layer 1 : Transaction Control - KCOCOTCT     

Opcode 1 : KTZFMT 

Opcode 2 : KTZRDH 

Opcode 3 : KTZARC

Opcode 4 : KTZREP

     

    Layer 2 : Transaction Read -  KCOCOTRD     

     

    Layer 3 : Transaction Update -  KCOCOTUP     

     

    Layer 4 : Transaction Block -  KCOCOTBK     [ktbcts.h]

         Opcode 1 : Block Cleanout 

         Opcode 2 : Physical Cleanout 

         Opcode 3 : Single Array Change

         Opcode 4 : Multiple Changes to an Array

         Opcode 5 : Format Block

     

    Layer 5 : Transaction Undo -  KCOCOTUN     [ktucts.h]

         Opcode 1 : Undo block or undo segment header - KTURDB

         Opcode 2 : Update rollback segment header - KTURDH

         Opcode 3 : Rollout a transaction begin 

         Opcode 4 : Commit transaction (transaction table update) 

- no undo record 

         Opcode 5 : Create rollback segment (format) - no undo record 

         Opcode 6 : Rollback record index in an undo block - KTUIRB

         Opcode 7 : Begin transaction (transaction table update) 

         Opcode 8 : Mark transaction as dead 

         Opcode 9 : Undo routine to rollback the extend of a rollback segment 

         Opcode 10 :Redo to perform the rollback of extend of rollback segment 

                    to the segment header. 

         Opcode 11 :Rollback DBA in transaction table entry - KTUBRB 

         Opcode 12 :Change transaction state (in transaction table entry) 

         Opcode 13 :Convert rollback segment format (V6 -> V7) 

         Opcode 14 :Change extent allocation parameters in a rollback segment 

         Opcode 15 :

         Opcode 16 :

         Opcode 17 :

         Opcode 18 :

         Opcode 19 : Transaction start audit log record

         Opcode 20 : Transaction continue audit log record     

         Opcode 24 : Kernel Transaction Undo Relog CHanGe - KTURLGU

    Layer 6 : Control File -  KCOCODCF     [tbs.h]

     

    Layer 10 : INDEX -  KCOCODIX     [kdi.h]

         Opcode 1 : load index block (Loader with direct mode) 

         Opcode 2 : Insert leaf row 

         Opcode 3 : Purge leaf row 

         Opcode 4 : Mark leaf row deleted 

         Opcode 5 : Restore leaf row (clear leaf delete flags) 

         Opcode 6 : Lock index block 

         Opcode 7 : Unlock index block 

         Opcode 8 : Initialize new leaf block 

         Opcode 9 : Apply Itl Redo 

         Opcode 10 :Set leaf block next link 

         Opcode 11 :Set leaf block previous link 

         Opcode 12 :Init root block after split 

         Opcode 13 :Make leaf block empty 

         Opcode 14 :Restore block before image 

         Opcode 15 :Branch block row insert 

         Opcode 16 :Branch block row purge 

         Opcode 17 :Initialize new branch block 

         Opcode 18 :Update keydata in row 

         Opcode 19 :Clear row's split flag 

         Opcode 20 :Set row's split flag 

         Opcode 21 :General undo above the cache (undo) 

         Opcode 22 :Undo operation on leaf key above the cache (undo) 

         Opcode 23 :Restore block to b-tree 

         Opcode 24 :Shrink ITL (transaction entries) 

         Opcode 25 :Format root block redo 

         Opcode 26 :Undo of format root block (undo) 

         Opcode 27 :Redo for undo of format root block 

         Opcode 28 :Undo for migrating block

         Opcode 29 :Redo for migrating block

         Opcode 30 :IOT leaf block nonkey update

         Opcode 31 :Cirect load root redo

         Opcode 32 :Combine operation for insert and restore rows 

     

    Layer 11 : Row Access -  KCOCODRW     [kdocts.h]

         Opcode 1 : Interpret Undo Record (Undo) 

         Opcode 2 : Insert Row Piece 

         Opcode 3 : Drop Row Piece 

         Opcode 4 : Lock Row Piece 

         Opcode 5 : Update Row Piece 

         Opcode 6 : Overwrite Row Piece 

         Opcode 7 : Manipulate First Column (add or delete the 1rst column) 

         Opcode 8 : Change Forwarding address 

         Opcode 9 : Change the Cluster Key Index 

         Opcode 10 :Set Key Links (change the forward & backward key links 

                    on a cluster key) 

         Opcode 11 :Quick Multi-Insert (ex: insert as select ...) 

         Opcode 12 :Quick Multi-Delete 

         Opcode 13 :Toggle Block Header flags 

     

    Layer 12 : Cluster -  KCOCODCL     [?]

    

    Layer 13 : Transaction Segment -  KCOCOTSG     [ktscts.h]

         Opcode 1 : Data segment format 

         Opcode 2 : Merge 

         Opcode 3 : Set link in block 

         Opcode 4 : Not used 

         Opcode 5 : New block (affects segment header) 

         Opcode 6 : Format block (affects data block) 

         Opcode 7 : Record link 

         Opcode 8 : Undo free list (undo) 

         Opcode 9 : Redo free list head (called as part of undo) 

         Opcode 9 : Format free list block (freelist group) 

         Opcode 11 :Format new blocks in free list 

         Opcode 12 :free list clear 

         Opcode 13 :free list restore (back) (undo of opcode 12) 

     

    Layer 14 : Transaction Extent -  KCOCOTEX     [kte.h]

         Opcode 1 : Add extent to segment 

         Opcode 2 : Unlock Segment Header 

         Opcode 3 : Extent DEaLlocation (DEL) 

         Opcode 4 : Undo to Add extent operation (see opcode 1) 

         Opcode 5 : Extent Incarnation number increment 

         Opcode 6 : Lock segment Header 

         Opcode 7 : Undo to rollback extent deallocation (see opcode 3) 

         Opcode 8 : Apply Position Update (truncate) 

         Opcode 9 : Link blocks to Freelist 

         Opcode 10 :Unlink blocks from Freelist 

         Opcode 11 :Undo to Apply Position Update (see opcode 8) 

         Opcode 12 :Convert segment header to 6.2.x type 

     

    Layer 15 : Table Space -  KCOCOTTS     [ktt.h]

        Opcode 1 : Format deferred rollback segment header 

        Opcode 2 : Add deferred rollback record 

        Opcode 3 : Move to next block 

        Opcode 4 : Point to next deferred rollback record 

     

    Layer 16 : Row Cache -  KCOCOQRC     

     

    Layer 17 : Recovery (REDO) -  KCOCORCV     [kcv.h]

         Opcode 1 : End Hot Backup : This operation clears the hot backup 

                    in-progress flags in the indicated list of files 

         Opcode 2 : Enable Thread : This operation creates a redo record 

                    signalling that a thread has been enabled 

         Opcode 3 : Crash Recovery Marker 

         Opcode 4 : Resizeable datafiles

         Opcode 5 : Tablespace ONline

         Opcode 6 : Tablespace OFFline

         Opcode 7 : Tablespace ReaD Write

         Opcode 8 : Tablespace ReaD Only

         Opcode 9 : ADDing datafiles to database

         Opcode 10 : Tablespace DRoP

         Opcode 11 : Tablespace PitR     

    Layer 18 : Hot Backup Log Blocks -  KCOCOHLB     [kcb.h]

         Opcode 1 : Log block image 

         Opcode 2 : Recovery testing 

     

    Layer 19 : Direct Loader Log Blocks - KCOCODLB     [kcbl.h]

         Opcode 1 : Direct block logging 

         Opcode 2 : Invalidate range 

         Opcode 3 : Direct block relogging

         Opcode 4 : Invalidate range relogging     

    Layer 20 : Compatibility Segment operations - KCOCOKCK  [kck.h]

         Opcode 1 : Format compatibility segment -  KCKFCS

         Opcode 2 : Update compatibility segment - KCKUCS

    Layer 21 : LOB segment operations - KCOCOLFS     [kdl2.h]

         Opcode 1 : Write data into ILOB data block - KDLOPWRI

    Layer 22 : Tablespace bitmapped file operations -  KCOCOTBF [ktfb.h]

 Opcode 1 : format space header - KTFBHFO

 Opcode 2 : space header generic redo - KTFBHREDO

 Opcode 3 : space header undo - KTFBHUNDO

 Opcode 4 : space bitmap block format - KTFBBFO

 Opcode 5 : bitmap block generic redo - KTFBBREDO 

    Layer 23 : write behind logging of blocks - KCOCOLWR [kcbb.h]

 Opcode 1 : Dummy block written callback - KCBBLWR

    Layer 24 : Logminer related (DDL or OBJV# redo) - KCOCOKRV [krv.h]

 Opcode : common portion of the ddl - KRVDDL

 Opcode : direct load redo - KRVDLR 

 Opcode : lob related info - KRVLOB

 Opcode : misc info - KRVMISC 

 Opcode : user info - KRVUSER

CV是組成REDO RECORD的基礎,REDO RECORD是由一組CV組成的,這組CV完成對資料庫的一個原子修改操作。舉個例子,一個REDO RECORD裡可能包含3CV,第一個是對UNDO SEGMENT HEADER的修改,第二個是對UNDO SEGMENT的修改,第三個是對DATA BLOCK的修改。而一個事務可能包含NREDO RECORD

當前臺程式要對某個資料塊進行修改的時候,首先要形成相關的CHANGE VECTOR,然後把多個CV組成REDO RECORD,把REDO RECORD寫入LOG BUFFER後,前臺程式可以將CV提交到相關的資料塊上。

下面我們透過一個實驗來學習一下REDO RECORDCHANGE VECTOR。我們設計的場景是,首先在SCOTT下建立一張表:SCOTT.T4

create table t4 (a integer);

然後我們檢視一下當前的SCN是什麼:

SQL> select max(ktuxescnw * power(2, 32) + ktuxescnb) from x$ktuxe;

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)
------------------------------------
                            16230857

執行一條INSERT語句,然後以這條語句進行分析:

insert into t4 values (1);

commit;

 SQL> select max(ktuxescnw * power(2, 32) + ktuxescnb) from x$ktuxe;

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)
------------------------------------
                            16230863

透過這兩個SCN值,我們對當前的REDO LOG進行DUMP

SQL> alter system dump logfile 'd:\oracle\oradata\ora92\redo01.log' scn min 16230857 scn max 16230863;

系統已更改。

下面的內容就是我們剛才的INSERT語句產生的,下面是第一個REDO RECORD

REDO RECORD - Thread:1 RBA: 0x0000a1.000040ce.0010 LEN: 0x0054 VLD: 0x01  

SCN: 0x0000.00f7a9c9 SUBSCN:  1 03/12/2008 09:37:49

CHANGE #1 TYP:0 CLS:33 AFN:2 DBA:0x00800111 SCN:0x0000.00f7a9c7 SEQ:  1 OP:5.4              

可以看到這個REDO RECORDRBA0x0000a1.000040ce.0010,翻譯成10進位制是161.64.16LOG SEQUENCE號是161,在REDO LOG中的塊號是64,起始位元組是塊內的16位元組。這個REDO RECORD的長度是84位元組(0X54)。

VLD:0X01表示這個REDO RECORD的型別,0X01表示CHANGE VECTOR COPIED IN。再往下看CHANGE #1,這就是這個REDO RECORD的第一個CV,我們看到OP:5.4,根據上面的OPCODE清單我們可以看到是Commit transaction (transaction table update) ,修改事務表,RDBA2/273透過DBA_EXTENTS查詢為_SYSSMU3$

繼續看下一個REDO RECORD:

REDO RECORD - Thread:1 RBA: 0x0000a1.000040cf.0010 LEN: 0x0058 VLD: 0x02  

SCN: 0x0000.00f7a9cb SUBSCN:  1 03/12/2008 09:37:57

CHANGE #1 MEDIA RECOVERY MARKER SCN:0x0000.00000000 SEQ:  0 OP:23.1   

 Block Written - afn: 2 rdba: 0x008095f1(2,38385)                     ----undo segment

                   scn: 0x0000.00f79cf1 seq: 0x02 flg:0x04

 Block Written - afn: 2 rdba: 0x0080726b(2,29291)                    ------undo segment 

                   scn: 0x0000.00f7a1e1 seq: 0x02 flg:0x04           ------undo segment

 Block Written - afn: 2 rdba: 0x00806e2e(2,28206)                    ------undo segment

                   scn: 0x0000.00f79d50 seq: 0x02 flg:0x04

這個REDO RECORD的VLD是2,含義是A commit SCN was allocated and is stored。說明這個REDO RECORD裡分配並儲存了一個SCN。我們來看第一個CHANGE:

  OP:23.1,OPCODE是23.1,Dummy block written callback - KCBBLWR,是產生一些WRITE BEHIND LOGGING資訊。下一個REDO RECORD開始是針對T4表的操作。

REDO RECORD - Thread:1 RBA: 0x0000a1.000040d0.0010 LEN: 0x00b8 VLD: 0x01  

SCN: 0x0000.00f7a9cd SUBSCN:  1 03/12/2008 09:38:03

CHANGE #1 TYP:0 CLS: 4 AFN:5 DBA:0x01401a63 SCN:0x0000.00f7a965 SEQ:  3 OP:13.28  

Redo on Level1 Bitmap Block  ---針對1ST BMB的操作

Redo to add range

bdba: Length: 16

CHANGE #2 TYP:0 CLS: 8 AFN:5 DBA:0x01401a61 SCN:0x0000.00f7a965 SEQ:  2 OP:13.22  ----dba (5/6753) -- scott.t4的segment header,設定高水位

Redo on Level1 Bitmap Block

Redo to set hwm

Opcode: 32      Highwater::  0x01401a71  ext#: 0      blk#: 16     ext size: 16    

  #blocks in seg. hdr's freelists: 0     

  #blocks below: 13    

  mapblk  0x00000000  offset: 0     

  

這個REDO RECORD是對SCOTT.T4的表頭的操作。下面連續幾個REDO RECORD是格式化資料塊,為了簡化起見,我只列出其中的一個:

REDO RECORD - Thread:1 RBA: 0x0000a1.000040d0.00c8 LEN: 0x003c VLD: 0x01

SCN: 0x0000.00f7a9cd SUBSCN:  1 03/12/2008 09:38:03

CHANGE #1 TYP:1 CLS: 1 AFN:5 DBA:0x01401a64 SCN:0x0000.00f7a9cd SEQ:  1 OP:13.21  --dba(5/6756) --scott.t4,格式化BLOCK

ktspbfredo - Format Pagetable Datablock 

Parent(l1) DBA: typ: 1 objd: 32027 itls: 2 fmt_flag: 0 poff: 0

中間我省略了幾個REDO RECORD,直接看看包含INSERT語句的REDO RECORD:

REDO RECORD - Thread:1 RBA: 0x0000a1.000040d2.00b0 LEN: 0x015c VLD: 0x01

SCN: 0x0000.00f7a9cd SUBSCN:  1 03/12/2008 09:38:03

CHANGE #1 TYP:0 CLS:23 AFN:2 DBA:0x00800071 SCN:0x0000.00f7a38b SEQ:  1 OP:5.2    ----Update rollback segment header - KTURDH SYS_SYSSMU2$

ktudh redo: slt: 0x000f sqn: 0x00004947 flg: 0x0012 siz: 80 fbi: 0

            uba: 0x008090cb.0550.13    pxid:  0x0000.000.00000000

CHANGE #2 TYP:0 CLS:24 AFN:2 DBA:0x008090cb SCN:0x0000.00f7a38a SEQ:  3 OP:5.1   ---Undo block or undo segment header - KTURDB  SYS_SYSSMU4$

ktudb redo: siz: 80 spc: 2746 flg: 0x0012 seq: 0x0550 rec: 0x13

            xid:  0x0004.00f.00004947  

ktubl redo: slt: 15 rci: 0 opc: 11.1 objn: 32027 objd: 32027 tsn: 5              ----Interpret Undo Record (Undo) ,針對scott.t4表生成UNDO資料

Undo type:  Regular undo        Begin trans    Last buffer split:  No 

Temp Object:  No 

Tablespace Undo:  No 

             0x00000000  prev ctl uba: 0x008090cb.0550.10 

prev ctl max cmt scn:  0x0000.00f78d7e  prev tx cmt scn:  0x0000.00f78f06 

KDO undo record:

KTB Redo 

op: 0x03  ver: 0x01  

op: Z                                                                          ------Undo of first (ever) change to the ITL,首先是對ITL的修改

KDO Op code: DRP row dependencies Disabled                                     ------Delete Row Piece

  xtype: XA  bdba: 0x01401a65  hdba: 0x01401a63                                  -----ROWID

itli: 1  ispac: 0  maxfr: 2401

tabn: 0 slot: 0(0x0)

CHANGE #3 TYP:0 CLS: 1 AFN:5 DBA:0x01401a65 SCN:0x0000.00f7a9cd SEQ:  2 OP:11.2   ---Insert Row Piece ,插入一條記錄

KTB Redo 

op: 0x01  ver: 0x01  

op: F  xid:  0x0004.00f.00004947    uba: 0x008090cb.0550.13                       ---First change to ITL by this TX. Copy redo to ITL

KDO Op code: IRP row dependencies Disabled                                        ---Single Insert Row Piece,行插入操作

  xtype: XA  bdba: 0x01401a65  hdba: 0x01401a63                                   ---對應的表是scott.t4

itli: 1  ispac: 0  maxfr: 2401

tabn: 0 slot: 0(0x0) size/delt: 6

fb: --H-FL-- lb: 0x1  cc: 1

null: -

col  0: [ 2]  c1 02                                                                                         ----十進位制1,就是我們插入的資料

CHANGE #4 MEDIA RECOVERY MARKER SCN:0x0000.00000000 SEQ:  0 OP:5.20              ----Transaction continue audit log record,記錄SESSION資訊

session number   = 11

serial  number   = 115

transaction name = 

這個事務的最後一個REDO RECORD就是COMMIT產生的記錄:

REDO RECORD - Thread:1 RBA: 0x0000a1.000040d4.0010 LEN: 0x0054 VLD: 0x01        

SCN: 0x0000.00f7a9cf SUBSCN:  1 03/12/2008 09:38:03

CHANGE #1 TYP:0 CLS:23 AFN:2 DBA:0x00800071 SCN:0x0000.00f7a9cd SEQ:  1 OP:5.4  ----Commit transaction (transaction table update) 

ktucm redo: slt: 0x000f sqn: 0x00004947 srt: 0 sta: 9 flg: 0x2 

ktucf redo: uba: 0x008090cb.0550.13 ext: 2 spc: 2664 fbi: 0 

 

REDO LOG的產生十分頻繁,幾乎每秒鐘都有幾百K到幾MRED LOG產生,甚至某些大型資料庫每秒鐘產生的REDO LOG量達到了10M以上。不過前臺程式每次產生的REDO量卻不大,一般在幾百位元組到幾K,而一般來所一個事務產生的REDO 量也不過幾K到幾十K。基於REDO產生的這個特點,如果每次REDO產生後就必須寫入REDO LOG檔案,那麼就會存在兩個問題,一個是REDO LOG檔案寫入的頻率過高,會導致REDO LOG檔案的IO存在問題,第二個是如果由前臺程式來完成REDO LOG的寫入,那麼會導致大量併發的前臺程式產生REDO LOG檔案的爭用。為了解決這兩個問題,OracleREDO LOG機制中引入了LGWR後臺程式和LOG BUFFER

LOG BUFFEROracle用來快取前臺程式產生的REDO LOG資訊的,有了LOG BUFFER,前臺程式就可以將產生的REDO LOG資訊寫入LOG BUFFER,而不需要直接寫入REDO LOG檔案,這樣就大大提高了REDO LOG產生和儲存的時間,從而提高資料庫在高併發情況下的效能。

既然前臺程式不將REDO LOG資訊寫入REDO LOG檔案了,那麼就必須要有一個後臺程式來完成這個工作。這個後臺程式就是LGWRLGWR程式的主要工作就是將LOG BUFFER中的資料批次寫入到REDO LOG檔案中。對於Oracle資料庫中,只要對資料庫的改變寫入到REDO LOG檔案中了,那麼就可以確保相關的事務不會丟失了。

引入LOG BUFFER後,提高了整個資料庫RDMBS寫日誌的效能,但是如何確保一個已經提交的事務確確實實的被儲存在資料庫中,不會因為之後資料庫發生故障而丟失呢?實際上在前面兩節中我們介紹的REDO LOG的一些基本的演算法確保了這一點。首先WRITE AHEAD LOG協議確保了只要儲存到REDO LOG檔案中的資料庫變化一定能夠被重演,不會丟失,也不會產生二義性。其次是在事務提交的時候,會產生一個COMMITCV,這個CV被寫入LOG BUFFER後,前臺程式會發出一個訊號,要求LGWR將和這個事務相關的REDO LOG資訊寫入到REDO LOG檔案中,只有這個事務相關的REDO LOG資訊已經確確實實被寫入REDO LOG檔案的時候,前臺程式才會向客戶端發出事務提交成功的訊息,這樣一個事務才算是被提交完成了。在這個協議下,只要客戶端收到了提交完成的訊息,那麼可以確保,該事務已經存檔,不���丟失了。LGWR會繞過作業系統的緩衝,直接寫入資料檔案中,以確保REDO LOG的資訊不會因為作業系統出現故障(比如當機)而丟失要求確保寫入REDO LOG檔案的資料。

實際上,雖然Oracle資料庫使用了繞過緩衝直接寫REDO LOG檔案的方法,以避免作業系統故障導致的資料丟失,不過我們還是無法確保這些資料已經確確實實被寫到了物理磁碟上。因為我們RDBMS使用的絕大多數儲存系統都是帶有寫緩衝的,寫緩衝可以有效的提高儲存系統寫效能,不過也帶來了另外的一個問題,就是說一旦儲存出現故障,可能會導致REDO LOG的資訊丟失,甚至導致REDO LOG出現嚴重損壞。儲存故障的機率較小,不過這種小機率事件一旦發生還是會導致一些資料庫事務的丟失,因此雖然Oracle的內部演算法可以確保一旦事務提交成功,事務就確認被儲存完畢了,不過還是可能出現提交成功的事務丟失的現象。

實際上,Oracle在設計REDO LOG檔案的時候,已經最大限度的考慮了REDO LOG檔案的安全性,REDO LOG檔案的BLOCK SIZE和資料庫的BLOCK SIZE是完全不同的,REDO LOG檔案的BLOCK SIZE是和作業系統的IO BLOCK SZIE完全相同的,這種設計確保了一個REDO LOG BLOCK是在一次物理IO中同時寫入的,因此REDO LOG BLOCK不會出現塊斷裂的現象。

瞭解LOG BUFFERLGWR的演算法,有助於我們分析和解決相關的效能問題,因此我們需要花一點時間來了解LOG BUFFER相關的基本演算法。用一句話來概括,LOG BUFFER是一個迴圈使用的順序型BUFFER。這句話裡包含了兩個含義,一個是LOG BUFFER是一個順序讀寫的BUFFERLOG BUFFER資料的寫入是順序的;第二個含義是LOG BUFFER是一個迴圈BUFFER,當LOG BUFFER寫滿後,會回到頭上來繼續寫入REDO LOG資訊。LOG BUFFER資料的寫入是由前臺程式完成的,這個寫入操作是併發的,每個前臺程式在生成了REDO LOG資訊後,需要首先在LOG BUFFER中分配空間,然後將REDO LOG資訊寫入到LOG BUFFER中去。LOG BUFFER中分配空間是一個序列的操作,因此Oracle在設計這方面的演算法的時候,把LOG BUFFER空間分配和將REDO LOG資料複製到LOG BUFFER中這兩個操作分離了,一旦分配了LOG BUFFER空間,就可以釋放相關的閂鎖,其他前臺程式就可以繼續分配空間了(這裡所說的前臺程式只是一個泛指,是為了表述方便而已,讀者一定要注意,因為後臺程式也會對資料庫進行修改,也需要產生REDO LOG資訊,後臺程式的REDO 操作和前臺程式是大體一致的)。

前臺程式寫入REDO 資訊會使LOG BUFFER的尾部指標不停的向前推進,而LGWR這個後臺程式不聽的從LOG BUFFER的頭部指標處開始查詢還未寫入REDO LOG檔案的LOG BUFFER資訊,並將這些資訊寫入REDO LOG檔案中,並且將BUFFER頭部指標不停的向後推進,一旦LOG BUFFER的頭部指標和尾部指標重合,那麼就說明了當前的LOG BUFFER是空的。而如果前臺程式在LOG BUFFER中分配空間會使LOG BUFFER的尾部指標一直向前推進,一旦LOG BUFFER的尾部指標追上了LOG BUFFER的頭部指標,那麼說明目前LOG BUFFER中無法分配新的空間給後臺程式了,後臺程式必須要等候LGWR將這些資料寫入REDO LOG檔案,然後向前推進了頭部指標,才可能再次獲得新的可用BUFFER空間。這個時候,前臺程式會等待LOG FILE SYNC事件。

為了讓LGWR儘快將LOG BUFFER中的資料寫入REDO LOG檔案,以便於騰出更多的空閒空間,Oracle資料庫設計了LGWR寫的觸發條件:

事務提交時

LOG BUFFER中的資料超過1M

LOG BUFFER中的資料超過了_log_io_size隱含引數指定的大小

每隔3秒鐘

前面我們多次提到了,當事務提交時,會產生一個提交的REDO RECORD,這個RECORD寫入LOG BUFFER後,前臺程式會觸發LGWR寫操作,這個時候前臺程式就會等待LOG FILE SYNC等待,直到LGWR將相關的資料寫入REDO LOG檔案,這個等待就會結束,前臺程式就會收到提交成功的訊息。如果我們的系統中,每秒的事務數量較大,比如幾十個或者幾百個,甚至大型OLTP系統可能會達到每秒數千個事務。在這種系統中,LGWR由於事務提交而被激發的頻率很高,LOG BUFFER的資訊會被很快的寫入REDO LOG檔案中。

而對於某些系統來說,平均每個事務的大小很大,平均每個事務生成的REDO LOG資料量也很大,比如1M甚至更高,平均每秒鐘的事務數很少,比如1-2個甚至小於一個,那麼這種系統中LGWR由於事務提交而被激發的頻率很低,可能導致REDO LOG資訊在LOG BUFFER中被大量積壓,oracle設計的LOG BUFFER中資料超過1MLGWR激發條件就是為了解決這種情況而設計的,當LOG BUFFER中的積壓資料很多時,雖然沒有事務提交,也會觸發LGWRBUFFER中的資料寫入REDO LOG檔案。

除此之外,Oracle還透過了_LOG_IO_SIZE這個隱含引數來進一步控制LGWR寫操作,當LOG BUFFER中的資料超過了這個隱含引數的規定的大小,也會觸發LGWR被激發。這個引數的預設值是LOG BUFFER大小的1/3,這個引數單位是REDO LOG BLOCK。這個引數可以控制當LOG BUFFER中有多少個資料塊被佔用時,就要觸發LGWR寫操作,從而避免LOG BUFFER被用盡。

如果一個系統很空閒,很長時間都沒有事務提交,LOG BUFFER的使用也很少,就可能會導致LOG BUFFER中的資料長期沒有被寫入REDO LOG檔案,帶來丟失資料的風險,因此Oracle還設計了一個LGWR寫的激發條件,設定了一個時間觸發器,每隔3秒鐘,這個觸發器都會被啟用,這個觸發器被啟用的時候,如果發現LOG BUFFER不是空的,並且LGWR不處於活躍狀態,就會產生一個事件,啟用LGWR

前面我們討論了LGWRLOG BUFFER的一些基本的演算法,那麼下面我們來討論討論LOG FILE SYNC等待事件。LOG FILE SYNC等待的含義是等待LGWRLOG BUFFER的資料寫入到REDO LOG檔案中。一般情況下,如果某個事務在做提交的時候,會等待LOG FILE SYNC,而沒有做提交操作的會話不需要等待LOG FILE SYNC,因為前臺程式只需要將REDO LOG資訊寫入到LOG BUFFER中就可以了,不需要等待這些資料被寫入REDO LOG檔案。不過如果前臺程式在分配LOG BUFFER的時候,如果發現LOG BUFFER的尾部已經追上了LOG BUFFER的頭部,那麼前臺程式就要等待LGWR程式將頭部的資料寫入REDO LOG檔案,然後釋放LOG BUFFER空間。這個時候,沒有做提交操作的前臺程式都會等待LOG FILE SYNC事件。這種情況下,加大LOG BUFFER就可能可以減少大部分的LOG FILE SYNC等待了。

加大LOG BUFFER的大小,可能會帶來另外一個問題,比如LOG BUFFER1M增加到30M(關於LOG BUFFER是否需要大於3M的問題,以前我們已經多次討論,因此在這裡不再討論了,大家只需要記住一點就可以了,LOG BUFFER大於3M浪費空間,對效能影響不大的觀點是錯誤的),那麼_LOG_IO_SIZE自動會從300K增加到10M,在一個平均每秒事務數較少,並且每個事務的REDO SIZE較大的系統中,觸發LGWR寫操作的LOG BUFFER資料量會達到1M。一般來說,在一個大型的OLTP系統裡,每次LGWR寫入REDO LOG檔案的大小在幾K到幾十K之間,平���LOG FILE SYNC的時間在1-10毫秒之間。如果平均每次寫入的資料量過大,會導致LOG FILE SYNC的等待時間變長。因此在這種情況下,就可能需要設定_LOG_IO_SIZE引數,確保LOG FILE SYNC等待不要過長。

如果每次寫入REDO LOG檔案的資料量也不大,而LOG FILE SYNC等待時間很吵,比如說超過100毫秒,那麼我們就要分析一下REDO LOG檔案的IO效能了,如果REDO LOG檔案IO效能不佳,或者該檔案所在的IO熱點較大,也可能導致LOG FILE SYNC等待時間偏大,這種情況,我們可以檢視後臺程式的LOG FILE PARALLEL WRITE這個等待事件,這個等待事件一般的等待時間為幾個毫秒,如果這個等待事件的平均等待時間較長,那麼說明REDO LOG檔案的IO效能不佳,需要將REDO LOG檔案放到IO量較小,效能較快的磁碟上。

OLTP系統上,REDO LOG檔案的寫操作主要是小型的,比較頻繁,一般的寫大小在幾K,而每秒鐘產生的寫IO次數會達到幾十次,數百次甚至上千次。因此REDO LOG檔案適合存放於IOPS較高的轉速較快的磁碟上,IOPS僅能達到數百次的SATA盤不適合存放REDO LOG檔案。另外由於REDO LOG檔案的寫入是序列的,因此對於REDO LOG檔案所做的底層條帶化處理,對於REDO LOG寫效能的提升是十分有限的。

 

當前臺程式在LOG BUFFER中分配空間的時候,實際上已經在REDO LOG檔案中預先分配了空間,如果REDO LOG檔案已經寫滿,無法再分配空間給前臺程式的時候,就需要做一次日誌切換,這個時候前臺程式會想LGWR發出一個日誌切換的請求,然後等待log file switch completion等待事件。

日誌切換請求發出後,CKPT程式會進行一次日誌切換CHECKPOINT,而LGWR開始進行日誌切換工作。首先LGWR程式會透過控制檔案中的雙向連結串列,查詢到一個可用的REDO LOG檔案,作為新的CURRENT REDO LOG 查詢新的CURRENT REDO LOG的演算法是要求該日誌是非ACTIVE的,並且已經完成了歸檔(如果是歸檔模式),oracle會優先使用unused狀態的REDO LOG組作為CURRENT REDO LOG

在做日誌切換時,首先要將LOG BUFFER中還沒有寫入REDO LOG檔案的REDO RECORD寫入當前的REDO LOG檔案,然後將最後一個REDO RECORDSCN作為本日誌檔案的HIGH SCN記錄在REDO LOG檔案頭中。這些操作完成後,就可以關閉當前日誌了。

完成了上一個步驟,就需要進行第二次控制檔案事務,將剛剛關閉的REDO LOG標識為ACTIVE,將新的當前REDO LOG標識為CURRENT,如果資料庫處於歸檔模式,還要將老的日誌組記錄到控制檔案歸檔列表記錄中(在V$ARCHIVE試圖中科看到),並且通知歸檔程式對該日誌檔案進行歸檔。當所有的歸檔程式都處於忙狀態的時候,並且歸檔程式總數沒有超過log_archive_max_processes的情況下,LGWR還會生成一個新的歸檔程式來對老的日誌檔案進行歸檔。

這些操作完成後,LGWR開啟新的日誌組的所有成員,並在檔案頭中記錄下初始化資訊。這些完成後,LGWR修改SGA中的標誌位,允許生成新的REDO LOG資訊。

老的日誌組目前還被標誌位ACTIVE,當DBWR完成了CHECKPOINT所要求的寫批次操作後,該日誌組的狀態會被標識為inactive

從上述日誌切換的步驟我們可以看出,日誌切換還是有很多工作要做的,而且在日誌切換開始到結束之間,日誌的生成是被完全禁止的,因此在這個期間,對資料庫的修改操作會被全部阻塞。這也是我們經常提到的:“日誌切換是一種較為昂貴的操作”。既然日誌切換十分昂貴,對系統效能的影響較大,那麼我們就應該想辦法減少日誌切換的數量,提高日誌切換的速度。

減少日誌切換的數量我們可以從兩個方面去考慮,一方面是減少日誌的產生量,一方面是加大日誌檔案的大小。

對於減少日誌產生量,常規的辦法不外乎使用NOLOGGING操作,使用BULK操作、使用DIRECT PATH WRITE操作等。不過大家要注意在歸檔模式下合非歸檔模式下,這些NOLOGGING操作的效果是不同的。Julian Dyke對常見的可以使用nologging模式的操作做過測試,測試的環境是在一個歸檔模式下的資料庫,對一張10萬記錄的表進行一系列的操作,測試結果如下:

 

 

大家可以看出NOLOGGING操作的效果還是十分明顯的。CTAS操作的REDO LOG量從14M下降到39K左右。老白做了一個類似的測試,測試環境是一個非歸檔模式的環境,透過DBA_OBJECTS生成了一張具有10萬條記錄的表,做CTAS的時候,在沒有使用NOLOGGING的情況下,產生的REDO大小是113352,在使用NOLOGGING的情況下,產生的REDO量是176840。從資料上看,NOLOGGING並沒有減少REDO的產生量,在非歸檔模式下,像CTAS這樣的操作本身產生的REDO 量就很有限,NOLOGGING也作用不大了。

我們可以看到,在歸檔模式下,DIRECT PATH WRITE操作的REDO量大幅度的減少了,這是什麼原因導致的呢?我們透過對REDO LOGDUMP來看一看INSERT /*+ APPEND */操作產生的REDO和普通的INSERT SELECT操作產生的REDO有什麼不同。

REDO RECORD - Thread:1 RBA: 0x0006d1.0000003f.0130 LEN: 0x2050 VLD: 0x01

SCN: 0x0000.053653c2 SUBSCN:  2 09/21/2010 09:56:40

CHANGE #1 MEDIA RECOVERY MARKER SCN:0x0000.00000000 SEQ:  0 OP:24.6

CHANGE #2 TYP:1 CLS: 1 AFN:10 DBA:0x0280c406 OBJ:122858 SCN:0x0000.053653bd SEQ:  1 OP:19.1

Direct Loader block redo entry

Block header dump:  0x00000000

 Object id on Block? Y

 seg/obj: 0x1dfea  csc: 0x00.53653bc  itc: 3  flg: E  typ: 1 - DATA

     brn: 0  bdba: 0x280c38a ver: 0x01 opc: 0

     inc: 0  exflg: 0

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc

0x01   0x0001.00a.000054d6  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

0x03   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

data_block_dump,data header at 0xb68ab288

===============

tsiz: 0x1f80

hsiz: 0xc8

pbl: 0xb68ab288

bdba: 0x00000000

     76543210

flag=--------

ntab=1

nrow=91

frre=-1

fsbo=0xc8

fseo=0x432

avsp=0x36a

tosp=0x36a

0xe:pti[0]      nrow=91 offs=0

0x12:pri[0]     offs=0x1f36

0x14:pri[1]     offs=0x1eea

0x16:pri[2]     offs=0x1ea1

0x18:pri[3]     offs=0x1e57

0x1a:pri[4]     offs=0x1e09

0x1c:pri[5]     offs=0x1dbe

0x1e:pri[6]     offs=0x1d69

0x20:pri[7]     offs=0x1d1e

0x22:pri[8]     offs=0x1cd2

0x24:pri[9]     offs=0x1c79

0x26:pri[10]    offs=0x1c2f

......

我們可以看到,在歸檔模式下,DIRECT PATH LOAD時是將整個資料塊放入REDO LOG中,從而既大幅度的減少了REDO 的產生量,又保證了不會丟失資料。在非歸檔模式下,DIRECT PATH LOAD產生的日誌為:

REDO RECORD - Thread:1 RBA: 0x0006d0.0000bf37.00fc LEN: 0x0060 VLD: 0x01

SCN: 0x0000.05364c72 SUBSCN:  2 09/21/2010 09:45:57

CHANGE #1 MEDIA RECOVERY MARKER SCN:0x0000.00000000 SEQ:  0 OP:24.6

CHANGE #2 INVLD AFN:10 DBA:0x0280c386 BLKS:0x0003 SCN:0x0000.05364c72 SEQ:  1 OP:19.2

Direct Loader invalidate block range redo entry

在這種情況下,如果資料庫產生故障,這個資料塊就無法恢復了,可能成為壞塊。

除了減少REDO LOG產生量以外,減少日誌切換還可以從加大REDO LOG檔案的大小入手。比如一個每秒鐘產生1M資料的系統,每分鐘產生的資料是60M,如果REDO LOG檔案的大小是120M,那麼平均2分鐘就會產生一次日誌切換,如果我們把REDO LOG檔案的大小增加到600M,那麼平均10分鐘產生一次日誌切換。

有些DBA擔心加大REDO LOG檔案後會增加資料丟失的機會。的確,REDO LOG檔案越大,一個REDO LOG檔案所包含的REDO RECORD的數量就越多,一旦整個REDO LOG檔案丟失或者損壞,可能丟失的資料量就會增加。實際上,整個REDO LOG丟失的可能性極小,最主要的可能性是REDO LOG檔案被誤刪。如果儲存出現故障,導致了REDO LOG檔案損壞,那麼受影響的肯定是所有的REDO LOG檔案,而不是某一個REDO LOG檔案,無論REDO LOG資訊是存在一個REDO LOG檔案中還是存在2REDO LOG檔案中,其結果是完全一樣的。

剩下一種情況就是最常見的情況了,就是伺服器突然當機,我們可以來分析一下伺服器當機這種情況,REDO LOG檔案的大小不同可能造成的資料丟失是否會不同。首先我們要了解一下伺服器當機時可能丟失的資料可能是哪些,如果是已經提交的資料,CHECKPOINT已經推進到的部分,是已經被寫入資料檔案了,這部分資料是無論如何都不會丟失的,REDO LOG是用來恢復最後一次CHECKPOINT到當機前被寫入REDO LOG檔案的那部分資料。由於REDO LOG檔案的寫入是順序的,因此無論這部分資料被寫入到一個檔案還是多個檔案,並不影響這部分資料的恢復。因此我們可以看出,REDO LOG檔案大小和伺服器當機丟失資料的數量是無關的。

透過前面的分析我們應該已經瞭解到,系統故障時只有當整個REDO LOG檔案損壞時,REDO LOG檔案的大小才可能與丟失的資料量有關。在絕大多數情況下,加大REDO LOG檔案的大小並不會增加資料丟失的機會。因此我們在考慮REDO LOG檔案大小的時候,基本上可以忽略這個資料丟失的多少的問題。

不過在某些情況下,我們在需要加大REDO LOG檔案大小的時候,要適當的考慮,一是存在DATA GUARD的情況下,為了減少FAILOVER時的資料丟失量,我們不宜將REDO LOG檔案設定的過大。另外在存在CDC或者流複製下游捕獲的環境下,也需要考慮REDO LOG檔案大小和捕獲延時的關係問題。

很多DBA都受過教育,就是REDO LOG切換的時間應該儘可能的不低於10-20分鐘,如果日誌切換間隔低於這個值,就要考慮加大REDO LOG檔案的大小。事實上,沒有任何鐵律,只要日誌切換並沒有對系統的效能和安全產生嚴重的影響,那麼哪怕1分鐘切換2次日誌又有什麼關係呢?

 

記得10多年前,老白為電信開發計費賬務系統,那個時候,電信的計費系統應該算是“海量”資料處理系統了,一個本地網可能擁有50萬電話使用者,其中20-30萬使用者是有長話業務許可權的,這些使用者每個月可能會產生500萬以上的長話話單。每個月底如何處理這些話單就是一個很大的挑戰了。1999年電總髮布了電信賬務系統業務規範,並且要求各個開發商將其開發的賬務系統統一到電總去測試,測試透過的才能夠發給入網許可。在參加測試的60多家企業中,老白設計的系統雖然使用者介面上做的比較醜陋(當時老白的公司一共不也就七八條槍,整個開發團隊只有7個人,因此UI方面比起動輒幾十人開發團隊的公司來說,是沒法比的),不過在整個賬務處理的效能上是首屈一指的。處理50萬長話使用者,500萬話單記錄的賬務處理中,總耗時4小時多一點,拿到了第一名,而第二名的成績是6個半小時,第三名的成績就已經是10小時開外了。當時老白能���勝出的法寶有兩個,一個是將50萬使用者資料一次性載入記憶體,在記憶體中透過B樹結構儲存,第二個就是使用了BULK操作。後來老白也和第二名的公司進行了溝通,他們和我們相似的地方是也做了50萬使用者資料的預裝載,但是他們沒有使用BULK操作。

BULK INSERT操作比普通INSERT的速度要快很多,這一點是很多使用過BULK操作的人都瞭解的,不過為什麼BULK操作會比較快呢?Oracle官方的說法是BULK操作的時候,USER程式和SQL引擎的互動次數會大大減少,因此BULK操作有較好的效能。老白也一直接受了這個觀點,不過有一點疑惑的是,BULK操作和普通操作的差異僅僅在於和SQL引擎的互動次數上嗎?難道BULK操作是一次性向SQL引擎提交一個SQLSQL引擎內部處理BULK操作的時候還是將整個陣列還原為一條一條的記錄去插入的嗎?還是說BULK INSERTOracle內部處理過程中有一些獨特的地方呢?

在研究REDO OPCODE的時候,老白發現了一些蛛絲馬跡,LAYER 11ROW ACCESS方面的,也就是處理行資料的。在LAYER 11中,有這樣一些操作:

  Layer 11 : Row Access -  KCOCODRW     [kdocts.h]

         Opcode 1 : Interpret Undo Record (Undo) 

         Opcode 2 : Insert Row Piece 

         Opcode 3 : Drop Row Piece 

         Opcode 4 : Lock Row Piece 

         Opcode 5 : Update Row Piece 

         Opcode 6 : Overwrite Row Piece 

         Opcode 7 : Manipulate First Column (add or delete the 1rst column) 

         Opcode 8 : Change Forwarding address 

         Opcode 9 : Change the Cluster Key Index 

         Opcode 10 :Set Key Links (change the forward & backward key links 

                    on a cluster key) 

         Opcode 11 :Quick Multi-Insert 

         Opcode 12 :Quick Multi-Delete 

         Opcode 13 :Toggle Block Header flags 

我們注意到,11..11的定義為Quick Multi-insert11.12Quick Multi-Delete,這兩個OPCODE是不是有可能和BULK操作有關呢?我們來做一個實驗,首先建立一張測試表:

drop table sm_histable0101;

CREATE TABLE SM_HISTABLE0101

(

  SM_ID              NUMBER(10)                 NOT NULL,

  SM_SUBID           NUMBER(3)                  NOT NULL,

  SERVICE_TYPE       VARCHAR2(6),

  ORGTON             NUMBER(3),

  ORGNPI             NUMBER(3),

  ORGADDR            VARCHAR2(21)               NOT NULL,

  DESTTON            NUMBER(3),

  DESTNPI            NUMBER(3),

  DESTADDR           VARCHAR2(21)               NOT NULL,

  PRI                NUMBER(3),

  PID                NUMBER(3),

  SRR                NUMBER(3),

  DCS                NUMBER(3),

  SCHEDULE           VARCHAR2(21),

  EXPIRE             VARCHAR2(21),

  FINAL              VARCHAR2(21),

  SM_STATUS          NUMBER(3),

  ERROR_CODE         NUMBER(3),

  UDL                NUMBER(3),

  SM_TYPE            NUMBER(10),

  SCADDRTYPE         NUMBER(3),

  SCADDR             VARCHAR2(21),

  MOMSCADDRTYPE      NUMBER(3),

  MOMSCADDR          VARCHAR2(21),

  MTMSCADDRTYPE      NUMBER(3),

  MTMSCADDR          VARCHAR2(21),

  SCHEDULEMODE       NUMBER(3),

  UD                 VARCHAR2(255),

  ID_HINT            NUMBER(10)                 NOT NULL,

  DELIVERCOUNT       NUMBER(10),

  L2CACHE            NUMBER(10),

  L2CACHEWRITECOUNT  NUMBER(10),

  SERVICE            NUMBER(10),

  NEWORGADDRESS      VARCHAR2(21),

  NEWDESTADDRESS     VARCHAR2(21)

);

然後建立兩個儲存過程redo1redo2,分別用於普通的插入操作和bulk插入操作:

create or replace procedure redo1 is

  TYPE T_SM_ID    IS TABLE OF          NUMBER(10)   INDEX BY BINARY_INTEGER;

  TYPE T_SM_SUBID IS TABLE OF          NUMBER(3)    INDEX BY BINARY_INTEGER;

  TYPE T_ORGADDR    IS TABLE OF        VARCHAR2(21) INDEX BY BINARY_INTEGER;

  TYPE T_DESTADDR   IS TABLE OF        VARCHAR2(21) INDEX BY BINARY_INTEGER;

  TYPE T_ID_HINT    IS TABLE OF        NUMBER(10)   INDEX BY BINARY_INTEGER;

  V_SM_ID         T_SM_ID;

  V_SM_SUBID      T_SM_SUBID;

  V_ORGADDR       T_ORGADDR;

  V_DESTADDR      T_DESTADDR;

  V_ID_HINT       T_ID_HINT;

  I INTEGER;

  VREDO1 INTEGER;

  vredo2 integer;

BEGIN

   FOR I IN 1.. 2000 

   LOOP

      V_SM_ID(I):=I;

      V_SM_SUBID(I):=12;

      V_ORGADDR(I):='444555565';

      V_DESTADDR(I):='555555';

      V_ID_HINT(I):=i;

   END LOOP;  

   select value  into vredo1 from v$sysstat where name = 'redo size';

   FOR I IN 1..2000 LOOP

     INSERT INTO SM_HISTABLE0101 (SM_ID,SM_SUBID,ORGADDR,DESTADDR,ID_HINT) VALUES

       (V_SM_ID(I),V_SM_SUBID(I),V_ORGADDR(I),V_DESTADDR(I),V_ID_HINT(I));

   END LOOP;

   COMMIT;

   commit;

   select value  into vredo2 from v$sysstat where name = 'redo size';

   select value  into vredo2 from v$sysstat where name = 'redo size';

   dbms_output.put_line('redo size:'||to_char(vredo2-vredo1));

   

END;

/

create or replace procedure redo2 is 

  TYPE T_SM_ID    IS TABLE OF          NUMBER(10)   INDEX BY BINARY_INTEGER;

  TYPE T_SM_SUBID IS TABLE OF          NUMBER(3)    INDEX BY BINARY_INTEGER;

  TYPE T_ORGADDR    IS TABLE OF        VARCHAR2(21) INDEX BY BINARY_INTEGER;

  TYPE T_DESTADDR   IS TABLE OF        VARCHAR2(21) INDEX BY BINARY_INTEGER;

  TYPE T_ID_HINT    IS TABLE OF        NUMBER(10)   INDEX BY BINARY_INTEGER;

  V_SM_ID         T_SM_ID;

  V_SM_SUBID      T_SM_SUBID;

  V_ORGADDR       T_ORGADDR;

  V_DESTADDR      T_DESTADDR;

  V_ID_HINT       T_ID_HINT;

  I INTEGER;

  VREDO1 INTEGER;

  vredo2 integer;

  n integer;

BEGIN

   n:=2000;

   FOR I IN 1.. N 

   LOOP

      V_SM_ID(I):=I;

      V_SM_SUBID(I):=12;

      V_ORGADDR(I):='444555565';

      V_DESTADDR(I):='555555';

     V_ID_HINT(I):=i;

   END LOOP;  

   select value  into vredo1 from v$sysstat where name = 'redo size';

   FORALL I IN 1..N 

     INSERT INTO SM_HISTABLE0101 (SM_ID,SM_SUBID,ORGADDR,DESTADDR,ID_HINT) VALUES

       (V_SM_ID(I),V_SM_SUBID(I),V_ORGADDR(I),V_DESTADDR(I),V_ID_HINT(I));

   COMMIT;

   commit;

   select value  into vredo2 from v$sysstat where name = 'redo size';

   select value  into vredo2 from v$sysstat where name = 'redo size';

   dbms_output.put_line('redo size:'||to_char(vredo2-vredo1));

END;

/

然後執行下面的程式碼進行測試:

set serveroutput on

truncate table sm_histable0101;

select max(ktuxescnw * power(2, 32) + ktuxescnb)  from x$ktuxe;

exec redo1;

select max(ktuxescnw * power(2, 32) + ktuxescnb)  from x$ktuxe;

truncate table sm_histable0101;

select max(ktuxescnw * power(2, 32) + ktuxescnb)  from x$ktuxe;

exec redo2;

select max(ktuxescnw * power(2, 32) + ktuxescnb)  from x$ktuxe;

測試結果如下:

Table truncated.

SQL> 

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)

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

                            87596111

SQL> redo size:707356

PL/SQL procedure successfully completed.

SQL> 

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)

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

                            87596151

SQL> truncate table sm_histable0101;

select max(ktuxescnw * power(2, 32) + ktuxescnb)  from x$ktuxe;

exec redo2;

select max(ktuxescnw * power(2, 32) + ktuxescnb)  from x$ktuxe;

Table truncated.

SQL> 

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)

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

                            87596178

SQL> redo size:138728

PL/SQL procedure successfully completed.

SQL> 

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)

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

                            87596195

從測試的結果來看,使用普通的INSERT操作,產生了707356 位元組的REDO LOG,而使用BULK INSERT,只產生了138728位元組的REDO LOG,REDO LOG產生量只有正常水平的1/5不到。看樣子BULK INSERT操作在ORACLE RDBMS內部的操作是完全不同的,應該是採用了我們前面猜測的QUICK MULTI-INSERT操作。透過DUMP REDO LOG我們來驗證一下:

SQL>  alter system dump logfile '/opt/oracle/oradata/orcl/redo01.log' scn min 87596178 scn max 87596195;

System altered.

我們來檢視DUMP出來的REDO資訊:

CHANGE #2 TYP:0 CLS:18 AFN:7 DBA:0x01c007f2 OBJ:4294967295 SCN:0x0000.05389bf0 SEQ:  2 OP:5.1

ktudb redo: siz: 396 spc: 5858 flg: 0x0012 seq: 0x2aba rec: 0x11

            xid:  0x0001.021.0000551d

ktubl redo: slt: 33 rci: 0 opc: 11.1 objn: 122951 objd: 122956 tsn: 0

Undo type:  Regular undo        Begin trans    Last buffer split:  No

Temp Object:  No

Tablespace Undo:  No

             0x00000000  prev ctl uba: 0x01c007f2.2aba.0f

prev ctl max cmt scn:  0x0000.05388a1f  prev tx cmt scn:  0x0000.05388a26

txn start scn:  0xffff.ffffffff  logon user: 0  prev brb: 29362160  prev bcl: 0 KDO undo record:

KTB Redo

op: 0x03  ver: 0x01

op: Z

KDO Op code: QMD row dependencies Disabled

  xtype: XA flags: 0x00000000  bdba: 0x00406482  hdba: 0x00406481

itli: 1  ispac: 0  maxfr: 4863

tabn: 0 lock: 0 nrow: 131

slot[0]: 0

slot[1]: 1

slot[2]: 2

slot[3]: 3

slot[4]: 4

slot[5]: 5

slot[6]: 6

slot[7]: 7

slot[8]: 8

slot[9]: 9

slot[10]: 10

slot[11]: 11

slot[12]: 12

slot[13]: 13

slot[14]: 14

slot[15]: 15

slot[16]: 16

slot[17]: 17

slot[18]: 18

slot[19]: 19

slot[20]: 20

slot[21]: 21

slot[22]: 22

slot[23]: 23

slot[24]: 24

從上面來看,UNDO的資料也和普通的INSERT操作不同,是一種批次方式的UNDO,對於同一個資料塊中的所有記錄,都產生在同一個UNDO的CHANGE VECTOR中。我們再來看看資料:

CHANGE #3 TYP:0 CLS: 1 AFN:1 DBA:0x00406482 OBJ:122956 SCN:0x0000.05389c94 SEQ:  3 OP:11.11

KTB Redo

op: 0x01  ver: 0x01

op: F  xid:  0x0001.021.0000551d    uba: 0x01c007f2.2aba.11

KDO Op code: QMI row dependencies Disabled

  xtype: XA flags: 0x00000000  bdba: 0x00406482  hdba: 0x00406481

itli: 1  ispac: 0  maxfr: 4863

tabn: 0 lock: 1 nrow: 131

slot[0]: 0

tl: 53 fb: --H-FL-- lb: 0x0  cc: 29

col  0: [ 2]  c1 02

col  1: [ 2]  c1 0d

col  2: *NULL*

col  3: *NULL*

col  4: *NULL*

col  5: [ 9]  34 34 34 35 35 35 35 36 35

col  6: *NULL*

col  7: *NULL*

col  8: [ 6]  35 35 35 35 35 35

col  9: *NULL*

col 10: *NULL*

col 11: *NULL*

col 12: *NULL*

col 13: *NULL*

col 14: *NULL*

col 15: *NULL*

col 16: *NULL*

col 17: *NULL*

col 18: *NULL*

col 19: *NULL*

col 20: *NULL*

col 21: *NULL*

col 22: *NULL*

col 23: *NULL*

col 24: *NULL*

col 25: *NULL*

col 26: *NULL*

col 27: *NULL*

col 28: [ 2]  c1 02

slot[1]: 1

tl: 53 fb: --H-FL-- lb: 0x0  cc: 29

確實使用了OP CODE:11.11,BULK INSERT 操作使用了批次資料插入機制,因此BULK INSERT的效能才能夠遠高於單條記錄操作。在我們以往進行的測試中,BULK操作一般都比單條操作快1倍以上,有的甚至能夠快2倍以上。

在REDO LAYER 11中,大家也許會發現一個問題,11.11是MULTI-INSERT,11.12是MULTI-DELETE,單獨少了MULTI-UPDATE,難道BULK UPDATE操作的實現機制和BULK INSERT有所不同嗎?我們透過一個實驗來看看BULK UPDATE是否能夠減少REDO的產生量。透過簡單的修改REDO1,REDO2兩個儲存過程,生成REDOU1,REDOU2這兩個儲存過程:

create or replace procedure redou1 is

  TYPE T_SM_ID    IS TABLE OF          NUMBER(10)   INDEX BY BINARY_INTEGER;

  TYPE T_SM_SUBID IS TABLE OF          NUMBER(3)    INDEX BY BINARY_INTEGER;

  TYPE T_ORGADDR    IS TABLE OF        VARCHAR2(21) INDEX BY BINARY_INTEGER;

  TYPE T_DESTADDR   IS TABLE OF        VARCHAR2(21) INDEX BY BINARY_INTEGER;

  TYPE T_ID_HINT    IS TABLE OF        NUMBER(10)   INDEX BY BINARY_INTEGER;

  V_SM_ID         T_SM_ID;

  V_SM_SUBID      T_SM_SUBID;

  V_ORGADDR       T_ORGADDR;

  V_DESTADDR      T_DESTADDR;

  V_ID_HINT       T_ID_HINT;

  I INTEGER;

  VREDO1 INTEGER;

  vredo2 integer;

BEGIN

   FOR I IN 1.. 2000 

   LOOP

      V_SM_ID(I):=I;

      V_SM_SUBID(I):=12;

      V_ORGADDR(I):='111111';

      V_DESTADDR(I):='2222';

      V_ID_HINT(I):=i;

   END LOOP;  

   select value  into vredo1 from v$sysstat where name = 'redo size';

   FOR I IN 1..2000 LOOP

     update SM_HISTABLE0101 SET ORGADDR=V_ORGADDR(I) WHERE ID_HINT=V_ID_HINT(I);

   END LOOP;

   COMMIT;

   commit;

   select value  into vredo2 from v$sysstat where name = 'redo size';

   select value  into vredo2 from v$sysstat where name = 'redo size';

   dbms_output.put_line('redo size:'||to_char(vredo2-vredo1));

   

END;

/

create or replace procedure redoU2 is 

  TYPE T_SM_ID    IS TABLE OF          NUMBER(10)   INDEX BY BINARY_INTEGER;

  TYPE T_SM_SUBID IS TABLE OF          NUMBER(3)    INDEX BY BINARY_INTEGER;

  TYPE T_ORGADDR    IS TABLE OF        VARCHAR2(21) INDEX BY BINARY_INTEGER;

  TYPE T_DESTADDR   IS TABLE OF        VARCHAR2(21) INDEX BY BINARY_INTEGER;

  TYPE T_ID_HINT    IS TABLE OF        NUMBER(10)   INDEX BY BINARY_INTEGER;

  V_SM_ID         T_SM_ID;

  V_SM_SUBID      T_SM_SUBID;

  V_ORGADDR       T_ORGADDR;

  V_DESTADDR      T_DESTADDR;

  V_ID_HINT       T_ID_HINT;

  I INTEGER;

  VREDO1 INTEGER;

  vredo2 integer;

  n integer;

BEGIN

   n:=2000;

   FOR I IN 1.. N 

   LOOP

      V_SM_ID(I):=I;

      V_SM_SUBID(I):=12;

      V_ORGADDR(I):='111111';

      V_DESTADDR(I):='2222';

     V_ID_HINT(I):=i;

   END LOOP;  

   select value  into vredo1 from v$sysstat where name = 'redo size';

   FORALL I IN 1..N 

     update SM_HISTABLE0101 SET ORGADDR=V_ORGADDR(I) WHERE ID_HINT=V_ID_HINT(I);

   COMMIT;

   commit;

   select value  into vredo2 from v$sysstat where name = 'redo size';

   select value  into vredo2 from v$sysstat where name = 'redo size';

   dbms_output.put_line('redo size:'||to_char(vredo2-vredo1));

END;

/

然後執行下面的過程:

select max(ktuxescnw * power(2, 32) + ktuxescnb)  from x$ktuxe;

exec redou1;

select max(ktuxescnw * power(2, 32) + ktuxescnb)  from x$ktuxe;

select max(ktuxescnw * power(2, 32) + ktuxescnb)  from x$ktuxe;

exec redou2;

select max(ktuxescnw * power(2, 32) + ktuxescnb)  from x$ktuxe;

redo size:578904

PL/SQL procedure successfully completed.

SQL> 

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)

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

                            87608317

SQL> SQL> SQL> 

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)

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

                            87608317

SQL> redo size:571168

PL/SQL procedure successfully completed.

SQL> 

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)

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

                            87610350

從上面的結果看,兩種操作產生的REDO量是基本上十分接近的,看樣子BULK UPDATE在REDO方面並沒有很大的改善。透過DUMP REDO LOG,我們進一步驗證一下這個測試結果:

CHANGE #3 TYP:2 CLS: 1 AFN:1 DBA:0x00406482 OBJ:122959 SCN:0x0000.0538cbfd SEQ:  1 OP:11.5

KTB Redo

op: 0x11  ver: 0x01

op: F  xid:  0x0003.011.0000724a    uba: 0x00800ac1.32bc.1e

Block cleanout record, scn:  0x0000.0538cbff ver: 0x01 opt: 0x02, entries follow...

  itli: 2  flg: 2  scn: 0x0000.0538cbfd

KDO Op code: URP row dependencies Disabled

  xtype: XA flags: 0x00000000  bdba: 0x00406482  hdba: 0x00406481

itli: 1  ispac: 0  maxfr: 4863

tabn: 0 slot: 0(0x0) flag: 0x2c lock: 1 ckix: 191

ncol: 29 nnew: 1 size: 0

col  5: [ 6]  31 31 31 31 31 31

我們看到了OP CODE 11.5,這是一個正常的單行UPDATE操作。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/9399028/viewspace-687535/,如需轉載,請註明出處,否則將追究法律責任。

相關文章