Oracle基本数据改变原理浅析(redo与undo)--oracle核心技术读书笔记一,redo--oracle
在oracle中我们做一些更新操作,oracle底层是怎么流转的呢,就是这篇文章要讲解的。一. 原理
假设我们在一个已经更新了很多条分散记录的OLTP系统中,更新了一行数据。那么这个更新的真实步骤如下:
1. 创建一个重做改变向量,描述如何往undo块插入一条undo记录(也就是描述undo块的改变)
2. 创建一个重做改变向量,描述数据块的改变(也就是数据的改变)
3. 合并这两个重做改变向量为一条日志记录,并写到重做日志缓冲区(便于今后重做)
4. 向undo块插入undo记录(便于今后回退)
5. 改变数据块中的数据(这里才真正改变数据)
下面我们通过一个例子来展示这个过程。
二. 实践
我们先创建一个表,然后更新表中数据第一个块的第3,4,5,条记录,并且在每更新一条后会穿插更新第二个块的一条记录。也就是这个更新操作会更新6条记录,他会改变每一个记录的第三列------ 一个varchar2类型的字段,将其由xxxxxx(小写6个字符)改变为YYYYYYYYYY(大写10个字符)。
1. cmd命令行 以sys 用户登录
2. 准备工作(创建几个存储过程,用来转储块,转储重做日志等)
这些脚本见:http://download.csdn.net/detail/liwenshui322/7912909
3. 准备工作(主要清除回收站删除信息,设置块读取代价,估值计算依据等)
start setenv
set timing off
execute dbms_random.seed(0)
drop table t1;
begin
begin execute immediate 'purge recyclebin'; --清空回收站
exception when others then null;
end;
begin
dbms_stats.set_system_stats('MBRC',8); --多块读取为8块
dbms_stats.set_system_stats('MREADTIM',26); --对块读取平均时间为26毫秒
dbms_stats.set_system_stats('SREADTIM',12); --单块读取平均时间为30毫秒
dbms_stats.set_system_stats('CPUSPEED',800); --cpu每秒可执行800,000,000个操作
exception
when others then null;
end;
begin execute immediate 'begin dbms_stats.delete_system_stats; end;'; --删除系统统计信息
exception when others then null;
end;
begin execute immediate 'alter session set "_optimizer_cost_model"=io'; --基于io来计算估值
exception when others then null;
end;
end;
/
4. 创建表与索引
create table t1
as
select
2 * rownum - 1 id,
rownum n1,
cast('xxxxxx' as varchar2(10)) v1,
rpad('0',100,'0') padding
from
all_objects
where
rownum <= 60
union all
select
2 * rownum id,
rownum n1,
cast('xxxxxx' as varchar2(10)) v1,
rpad('0',100,'0') padding
from
all_objects
where
rownum <= 60
;
create index t1_i1 on t1(id);
5. 统计表信息
begin dbms_stats.gather_table_stats( ownname => user, tabname =>'T1', method_opt => 'for all columns size 1' ); end; /
6.查看表占用的块情况,和每一个块有多少条数据
select dbms_rowid.rowid_block_number(rowid) block_number, count(*) rows_per_block from t1 group by dbms_rowid.rowid_block_number(rowid) order by block_number ;
我们会看到,总共占用两个块,每一个块都有60条记录
7. 转储数据块
alter system switch logfile;
execute dump_seg('t1')
8. 做更新
update /*+ index(t1 t1_i1) */ t1 set v1 = 'YYYYYYYYYY' where id between 5 and 9 ;9. 转储更新块之后的数据块和undo块(发生检查点语句执行后,下一个语句等5,6s再执行,发生检查点只是告诉oracle将脏数据写入磁盘,需要一点时间)
pause Query the IMU structures now (@core_imu_01.sql)
alter system checkpoint;--发生检查点,让数据写到磁盘
execute dump_seg('t1')
execute dump_undo_block 10. 转储redo块rollback; commit; execute dump_log11. 定位转储信息文件位置
select sid from v$mystat where rownum=1;--查询结果传入下一个sql
SELECT d.value||'/'||lower(rtrim(i.instance, chr(0)))||'_ora_'||p.spid||'.trc' trace_file_name
from
( select p.spid from v$session s, v$process p
where s.sid='133' and p.addr = s.paddr) p,
( select t.instance from v$thread t,v$parameter v
where v.name = 'thread' and (v.value = 0 or t.thread# = to_number(v.value))) i,
( select value from v$parameter where name = 'user_dump_dest') d; 12. 打开文件
下面看几个关键部分,我们看第一个块的第5条数据,我们将这一行数据的第三列由xxxxxx改成了YYYYYYYYYY。
update之前:
tab 0, row 4, @0x1d3f tl: 117 fb: --H-FL-- lb: 0x0 cc: 4 col 0: [ 2] c1 0a col 1: [ 2] c1 06 col 2: [ 6] 78 78 78 78 78 78 col 3: [100] 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30我们看到col2长度是6,然后是6个78(x的十六进制ASCII码是78)。
update之后:
tab 0, row 4, @0x2a7 tl: 121 fb: --H-FL-- lb: 0x2 cc: 4 col 0: [ 2] c1 0a col 1: [ 2] c1 06 col 2: [10] 59 59 59 59 59 59 59 59 59 59 col 3: [100] 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30我们可以看到col2长度变成了10,是10个59(Y的十六进制ASCII码是59),同时我们看到行地址由@0x1d3f变成了@0x2a7,说明这一行的空间容不下新增的数据,换了一个地方。(检查行目录也能发现这一点)同时,我们能看到lb(lock byte)由0x0变成了0x2,表明这条记录被该块事务槽列表中的第二个事务槽所标识的事务锁定。事务槽可以在块首部看到。
下面,看第5条数据在redo里面保存的是什么(怎么保证数据的重做),在文件里面搜索 tabn: 0 slot: 4(0x4) flag: 0x2c lock: 2, 我们会找到这么一段描述
CHANGE #6 TYP:0 CLS:1 AFN:1 DBA:0x004161c9 OBJ:77125 SCN:0x0000.002796b6 SEQ:2 OP:11.5 ENC:0 RBL:0 KTB Redo op: 0x02 ver: 0x01 compat bit: 4 (post-11) padding: 0 op: C uba: 0x00c0055a.0123.27 KDO Op code: URP row dependencies Disabled xtype: XA flags: 0x00000000 bdba: 0x004161c9 hdba: 0x004161c8 itli: 2 ispac: 0 maxfr: 4863 tabn: 0 slot: 4(0x4) flag: 0x2c lock: 2 ckix: 50 ncol: 4 nnew: 1 size: 4 col 2: [10] 59 59 59 59 59 59 59 59 59 59这描述的是一个改变世界,我们看第6行 op code:是URP(更新行片),第七行我们可以看到更新的块地址bdba和所在段的地址hdba。
第八行itli: 2 表明执行更新操作的事务正在使用第二个事务槽,跟数据块里面看到的一致。
第九行tabn: 0 slot: 4 表明我们在更新第一张表(一个块可能存储多个表的数据)的第5条记录。
最后两行,我们可以看出这条记录有4列(nclo:4),修改了一列(nnew:1),长度增加了4(size:4).并将第3列的值改成了YYYYYYYYYY。(保存了修改后的数据,方便重做)
接下来,看第5条数据在undo里面怎么保存的(怎么保证数据的回退),在文件里面搜索tabn: 0 slot: 4(0x4) flag: 0x2c,我们会找到如下一段描述:
*----------------------------- * Rec #0x27 slt: 0x04 objn: 77125(0x00012d45) objd: 77125 tblspc: 0(0x00000000) * Layer: 11 (Row) opc: 1 rci 0x26 Undo type: Regular undo Last buffer split: No Temp Object: No Tablespace Undo: No rdba: 0x00000000 *----------------------------- KDO undo record: KTB Redo op: 0x02 ver: 0x01 compat bit: 4 (post-11) padding: 0 op: C uba: 0x00c0055a.0123.25 KDO Op code: URP Disabled row dependencies xtype: XA flags: 0x00000000 bdba: 0x004161c9 hdba: 0x004161c8 itli: 2 ispac: 0 maxfr: 4863 tabn: 0 slot: 4(0x4) flag: 0x2c lock: 0 ckix: 50 ncol: 4 nnew: 1 size: -4 col 2: [ 6] 78 78 78 78 78 78
主要关注下面的六行数据,其实跟前面redo里面的数据差不多,就是size=-4,col2变成了6个78(x的十六进制ASCII码是78)。(保证数据能够回去以前的版本)
最后,我们可以在转储的redo里面寻找undo块改变的描述,文件里面搜索tabn: 0 slot: 4(0x4) flag: 0x2c lock: 0,我们会找到如下一段描述:
CHANGE #11 TYP:0 CLS:36 AFN:3 DBA:0x00c0055a OBJ:4294967295 SCN:0x0000.002796b6 SEQ:4 OP:5.1 ENC:0 RBL:0
ktudb redo: siz: 92 spc: 4078 flg: 0x0022 seq: 0x0123 rec: 0x27
xid: 0x000a.004.00000467
ktubu redo: slt: 4 rci: 38 opc: 11.1 objn: 77125 objd: 77125 tsn: 0
Undo type: Regular undo Undo type: Last buffer split: No
Tablespace Undo: No
0x00000000
KDO undo record:
KTB Redo
op: 0x02 ver: 0x01
compat bit: 4 (post-11) padding: 0
op: C uba: 0x00c0055a.0123.25
KDO Op code: URP row dependencies Disabled
xtype: XA flags: 0x00000000 bdba: 0x004161c9 hdba: 0x004161c8
itli: 2 ispac: 0 maxfr: 4863
tabn: 0 slot: 4(0x4) flag: 0x2c lock: 0 ckix: 50
ncol: 4 nnew: 1 size: -4
col 2: [ 6] 78 78 78 78 78 78 第五行,代表这是一个undo块改变的描述,我们可以看到倒数几行跟undo里面的数据非常相似,因为这里记录的就是undo块的改变。
自此,我们基本上可以看清楚oracle是怎么描述数据的改变,然后才去真正去改变数据的。
1、停电的情况,事务不会被提交。实际上,如果细说redo apply和undo apply过程的话,是这样的:数据库先用redo恢复数据文件(包括普通permanent表空间的数据文件和undo表空间的数据文件),这就是你说的“恢复系统到失败点”,这之后开数据库实例会被打开,实例打开后,实例会根据undo中记录的信息回滚未提交的事务。
以下是原厂手册中的说明:
In the context of recovery, the undo information is used to undo the effects of uncommitted transactions, once all the datafile changes from the redo logs have been applied to the datafiles. The database is actually opened before the undo is applied.
You should not have to concern yourself with undo segments or manage them directly as part of your backup and recovery process.
2、磁盘损坏的情况,这种恢复也叫介质恢复,在恢复(recover)之前,需要使用备份先还原(restore)损坏的文件。
1、记录数据库的变化,比如update、insert、delete等动作。
2、并非直接写入undo表空间,只有当脏数据块达到一定程度才写入。