实验所用表:
create table jj_10(id number(5),name char(2000)) tablespace jj_ts_1;
insert into jj_10 values(1,'aa');
insert into jj_10 values(2,'bb');
insert into jj_10 values(3,'bb');
insert into jj_10 values(4,'cc');
insert into jj_10 values(5,'dd');
commit;
查看每一行所对应的文件号,块号:
SQL> select dbms_rowid.rowid_relative_fno(rowid),dbms_rowid.rowid_block_number(rowid),id from jj_10;
DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) ID
------------------------------------ ------------------------------------ ----------
7 402 1
7 402 2
7 402 3
7 403 4
7 403 5
此时发出一条SQL声明:
SQL> update jj_10 set name='abcd' where id=1;
已更新 1 行。
当update声明发出后:
步骤一,oracle所修改的第一个数据块,是事务表所在的回滚段头块.oracle会首先在事务表中记录事务信息.按如下步骤操作可以找到回滚段头中记录的事务开始信息:
(1):查看v$transaction视图,来观察当前的事务信息:
SQL> select ubafil,ubablk,xidusn,xidslot,xidsqn,start_scnb from v$transaction;
UBAFIL UBABLK XIDUSN XIDSLOT XIDSQN START_SCNB
---------- ---------- ---------- ---------- ---------- ----------
5 4308 22 12 124 2306598
(2)通过上面的XIDUSN列可以知道当前事务占用22号回滚段,在显示当前所有的回滚段名字:
SQL> select * from v$rollname ;
USN NAME
---------- ------------------------------
0 SYSTEM
11 _SYSSMU11$
12 _SYSSMU12$
13 _SYSSMU13$
21 _SYSSMU21$
22 _SYSSMU22$
23 _SYSSMU23$
24 _SYSSMU24$
已选择8行。
根据上面的显示结果,第22号回滚段名字是: _SYSSMU22$
(3)转储回滚段头:
SQL> alter system dump undo header '_SYSSMU22$';
index state cflags wrap# uel scn DBA parent-xid nub stmt_num cmt
------------------------------------------------------------------------------------------------
(..................)
0x0c 10 0x80 0x007c 0x0002 0x0000.00233226 0x014010d4 0x0000.000.00000000 0x00000001 0x00000000 0
(..................)
这其中比较重要的index和wrap#再加上回滚段的编号,构成了事务的XID,此处即:22.12.124. DBA列,事务所占用的最后一个回滚块地址:0x014010d4(即5号文件4308号块), 还有事务开始的scn 2306598
步骤二,oracle所修改的第二个数据块,是回滚块,oracle要在回滚块中记录前映象.在v$transaction 视图中,UBAFIL列和UBABLK列,列出了当前事务所对应的回滚块的文件号和块号.此处为5号文件4308号块,下面转储他;
SQL> alter system dump datafile 5 block 4308;
会话已更改.
回滚块的转储内容比较多,比较重要的项目有:
KTB Redo
op: 0x04 ver: 0x01
op: L itl: xid: 0x0018.028.00000044 uba: 0x01401111.0039.01
flg: C--- lkc: 0 scn: 0x0000.00230101[以上两行是表块中被当前事务覆盖的ITL槽信息,他的作用详见晶晶实验六之手动构造CR块]
Array Update of 1 rows:
tabn: 0 slot: 0(0x0)[行号,即块中0号行也就是第一行] flag: 0x2c lock: 0 ckix: 8
ncol: 2 nnew: 1 size: 0
KDO Op code: 21 row dependencies Disabled
xtype: XAxtype KDO_KDOM2 flags: 0x00000080 bdba: 0x01c00192[数据块的地址,即7号文件402号块] hdba: 0x01c00191[所修改表的段头块地址] itli: 2 ispac: 0 maxfr: 4858
vect = 3
col 1: [2000]--{列号,即1号列也就是第2列数据}
块的前映象:61 61 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20...(即aa 20表示空格).
步骤三,oracle所修改的第三个块是表真正的数据块,也就是真正的更改.
可转储7号文件,402号块观察.详细转储步骤省略...
我们上面的操作中没有涉及重作信息,下面把相关重作的信息补充上去:
每对一个数据块的更新,oracle都会生成他的重作,无论更新的块是回滚段头,回滚段块还是普通的表块.也就是说,前面的三个步骤改变了3个块,oracle就要为他们分别生成三条重做信息,这三条重做信息合在一起,被称为一条重做记录。而其中的每一条重做信息又被称为一条重做向量。(也就是1条重作记录,三个更改向量.)经试验,一个DML操作生成一条重作记录,假如一个DML操作更改3个记录,那么重作记录应该是一个,不过会有多个更改向量.
名词解释: 回滚块延伸--事务所产生的回滚信息填满一个块,并且开始占用下一个回滚块.就是一次回滚块延伸.在事务表中有一个DBA列,专门记录事务最后所占回滚块的编号.每发生一次回滚块延伸,都要修改事务表中此DBA的值.
另外,段头.也就是事务表,可能会随着回滚块的延伸而更改.完整点说,假如一个DML操作更改3条记录,并且没有回滚块延伸发生.也就是说事务表没有更改,那么会产生1个重做记录,7个更改项量,如果发生回滚块延伸了,那么再加一条回滚块延伸所引起的事务表变化的回滚块的后映象.(8个更改向量).
****综上所述,上面的例子生成一条重作记录,包含3条重作向量,分别对应回滚段头的重做,回滚段块的重做,表数据块的重做,下面详细观察一下:
可以使用v$log视图,查看当前重做日志的组号,然后再决定转储哪一个重做日志文件,转储命令如下:
alter system dump logfile '当前日志文件位置和名字'
////////////////这是重作记录头.其中有非常重要的rba 信息//////////////
REDO RECORD - Thread:1 RBA: 0x0002a8.0000040a.0010 LEN: 0x11a0 VLD: 0x05
SCN: 0x0000.00233226 SUBSCN: 1 03/01/2008 16:57:10
//////////////这是第一个重作向量,注意他的OP(操作代码)是5.2,这通常代表这是一个事务表的重做信息////////
CHANGE #1 TYP:0 CLS:59 AFN:5 DBA:0x01400051 OBJ:4294967295 SCN:0x0000.00233207 SEQ: 1 OP:5.2
ktudh redo: slt: 0x000c sqn: 0x0000007c flg: 0x000a siz: 2176 fbi: 140
uba: 0x014010d4.00e6.01 pxid: 0x0000.000.00000000
/////////////操作代码-5.1,表示此重作向量,是针对回滚段块的///////////
CHANGE #2 TYP:1 CLS:60 AFN:5 DBA:0x014010d4 OBJ:4294967295 SCN:0x0000.00233225 SEQ: 1 OP:5.1
ktudb redo: siz: 2176 spc: 0 flg: 0x000a seq: 0x00e6 rec: 0x01
xid: 0x0016.00c.0000007c
( ...省略部分信息.. )
tabn: 0 slot: 0(0x0) flag: 0x2c lock: 0 ckix: 8
ncol: 2 nnew: 1 size: 0
KDO Op code: 21 row dependencies Disabled
xtype: XAxtype KDO_KDOM2 flags: 0x00000080 bdba: 0x01c00192 hdba: 0x01c00191
itli: 2 ispac: 0 maxfr: 4858
vect = 3
col 1: [2000]
61 61 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20...
(这就是刚才回滚段块中的信息.)
/////////////重做向量3,操作代码11.19,代表这是一个更新的后映象////////////
CHANGE #3 TYP:2 CLS: 1 AFN:7 DBA:0x01c00192 OBJ:52462 SCN:0x0000.002331e9 SEQ: 1 OP:11.19
tabn: 0 slot: 0(0x0) flag: 0x2c lock: 2 ckix: 8
ncol: 2 nnew: 1 size: 0
KDO Op code: 21 row dependencies Disabled
xtype: XAxtype KDO_KDOM2 flags: 0x00000080 bdba: 0x01c00192 hdba: 0x01c00191
itli: 2 ispac: 0 maxfr: 4858
vect = 3
col 1: [2000]
61 62 63 64 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20...
(61 62 63 64 这是我的更新命令的结果.我上面的更新命令就是将此列更新为了'abcd'.)
上面所说的没有涉及提交.提交会单独作为另外一条重做记录.主要包含事务提交时的SCN,UBA,以及事务XID信息.提交的重做记录只有一个更改向量.
REDO RECORD - Thread:1 RBA: 0x0002ac.00000620.0010 LEN: 0x008c VLD: 0x05
SCN: 0x0000.002365e3 SUBSCN: 1 03/01/2008 22:07:58
CHANGE #1 TYP:0 CLS:37 AFN:5 DBA:0x01400009 OBJ:4294967295 SCN:0x0000.002365e1 SEQ: 1 OP:5.4
ktucm redo: slt: 0x0013 sqn: 0x00000083 srt: 0 sta: 9 flg: 0x2
ktucf redo: uba: 0x014010d4.00e6.01 ext: 14 spc: 2036 fbi: 0
小结racle有一个原则,记录重做信息的操作,应该早于相应的DML操作.当一条更新命令发出后,首先在重作中生成回滚段头对事务表操作的后映象,再实际的去更改相应的事务表.其次,在重作中生成回滚块的后映象,再修改响应的回滚块.最后,在重做中生成表块的后映象.再真正修改表块.
***为什么要有日志呢?
首先,oracle不能直接对数据文件进行操作,所有对数据的读写操作都要先经过buffer cache,如果读一个块,oracle会先检测buffer cache 中有没有这个块,如果有了,就直接在buffer cache 中读取,如果没有,要先从磁盘中将块读进buffer cache中,再从buffer cache中读取行,修改也是如此.在修改之前,先看buffer cache 中有没有要修改的块,如果没有,要将块读进 buffer cache 然后在buffer cache中修改.只要在内存中完成了修改,修改操作就算完成了.oracle并不把修改的数据真正被写进磁盘,当作修改的完成.也就是说,当oracle的修改完成后,被修改的数据还在内存中,没有写进磁盘,如果此时出现意外,宕机.用户修改的数据就有可能丢失,日志就有了用武之地,oracle用日志来保证'已经完成的修改',不会丢失.在修改被反映到buffer cache前,会先在log buffer 生成修改的重做信息.具体的形式,上面有描述咯.log buffer会被极其频繁的写进日志文件中.即使出现了意外宕机,可以使用日志文件中的重作信息去恢复数据.
如果oracle的修改是直接针对磁文件的,而不是先内存后文件,日志也就没有了用武之地.那oracle为什么不直接对数据文件进行修改呢?主要有两个原因:
1,重作的信息量比较少,由于oracle的最小I/O单位是数据块.如果你修改了一个块中的10个字节,那么oracle不会只读写这10个字节,他将以块为单位进行读写,但重作就不一样了,如果你只修改了10个字节,oracle只针对这10个字节生成重作信息.
2,日志是连续的,象流水一样,按照你操作的先后顺序,其相应的重作信息依次排列,因此,日志的写只需要定位一次磁头,可以连续的写.而数据文件是离散的,有可能我操作了N个块,这N个块互不相连,磁头就需要定位N次,而磁头的定位是非常耗时的一个操作,从这一点上来说,日志的写也比数据文件的写要快.
基于以上两点原因.oracle在写数据文件时,并不直接对数据文件写,而是在内存中完成写之后,就算完成了.而用日志对尚在内存中还没有来急写进磁盘的数据进行保护. |
|