ITPub博客

首页 > Linux操作系统 > Linux操作系统 > (转)老白的理解REDO LOG

(转)老白的理解REDO LOG

原创 Linux操作系统 作者:xz43 时间:2011-02-19 00:16:05 0 删除 编辑

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/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2010-11-16

  • 博文量
    420
  • 访问量
    1742487