Oracle基本数据改变原理浅析(redo与undo)--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_log

11. 定位转储信息文件位置

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是怎么描述数据的改变,然后才去真正去改变数据的。

时间: 2024-08-25 18:48:12

Oracle基本数据改变原理浅析(redo与undo)--oracle核心技术读书笔记一的相关文章

Oracle redo 复杂度--oracle核心技术读书笔记三

一. 概述 我们知道,在oracle中,每修改一条数据都会生成一条重做数据(也就是redo,里面记录了修改后的内容).目的就是为了将修改的数据备份,方便今后重做.现在有一个问题.oracle中只要修改数据,都会生成redo,这些redo会存放在一个叫做重做日志缓冲区里面.如果同时多个回话在修改数据,都要往重做日志缓冲区写入内容,就存在为同一片内存区域竞争的问题.存在竞争,就存在开销,这篇文章大概介绍一下,oracle如何尽量降低这种开销. 二.  问题概述 oracle中不断地修改数据,源源不断

oracle分页查询原理浅析

原因一 oracle默认为每个表生成rowmun,rowid字段,这些字段我们称之为伪列 1 创建测试表 CREATE TABLE TEST( ID NUMBER, NAME VARCHAR2(20) ) 2 插入测试数据 INSERT INTO TEST VALUES (1,'张三'); INSERT INTO TEST VALUES (2,'李四'); INSERT INTO TEST VALUES (3,'王五'); INSERT INTO TEST VALUES (4,'赵六'); IN

oracle undo 复杂度--oracle核心技术读书笔记四

一. 概述 undo 保存的是旧数据.比方.你改动了一条记录将A列abc改动为def.那么undo里面保存的就是abc.目的有两个:1. 假设你的事务没有提交.可是已经将A列改动,那么别人读取这条数据的时候,不应该可以看到你改动后的内容def,应该还仅仅能看到abc,这个时候就须要去读取undo,才干取到abc. 2. 假设你的事务后来失败,须要将A列由改动过的值def回退到之前的值abc.abc也要从undo里面去取. 这篇文章.简要介绍一下读一致性(也就是别人无法读取到你改动的未提交的内容)

Oracle事务原理探究1--oracle核心技术读书笔记五

1. 冲突解决 假如有一个系统只有你和我两个用户,并且我们都在持续对系统中一小部分数据做修改和查询操作. 如果你正在数据库中做一批修改操作,而我正在做查询,我一定不能看到你所做的修改,直到你告诉我可以看到你所做的所有更改才行(你提交了事务).因此在oracle内部,必须有一个高效的办法来识别哪些数据我可以看到,哪些数据我不可以看到. 从相反的角度来看,在你提交事务的时候,你需要一种高效的机制让其他所有人能够看到事务已经提交(也就是要告诉别人你所有修改过的数据都是可见的了).更极端一点的情况是,你

oracle如何保证事务的ACID原则--oracle核心技术读书笔记二

在事务中有四个通用的原则是所有数据库都必须遵守的,简称ACID原则,下面简单概述一下oracle是怎样实现这四个原则的. 一. 原子性(Atomicity):一个事务要么全部执行,要么全部都不执行 在oracle中,当我们做一次变更的时候,系统会自动创建一条undo记录来描述怎样撤销这次变更.也就是说,当执行到一个事务的中间时,如果有其他用户想访问我们更改过的数据,他必须使用undo记录来查看变更前的旧数据,即只有当我们提交这个事务后,所做的更改才能被他人看到.这样就可以确保,其他用户要么能看到

【知识点整理】Oracle中NOLOGGING、APPEND、ARCHIVE和PARALLEL下,REDO、UNDO和执行速度的比较

[知识点整理]Oracle中NOLOGGING.APPEND.ARCHIVE和PARALLEL下,REDO.UNDO和执行速度的比较 1  BLOG文档结构图 2  前言部分 2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~: ① 系统和会话级别的REDO和UNDO量的查询 ② NOLOGGING.APPEND.ARCHIVE和PARALLEL下,REDO.UNDO和执行速度的比较(重点)   Tips: ① 本文

09 redo and undo

本章提要-----------------------------------------------redo, undo 定义redo, undo 如何工作如何访问 redo, undo提交和回滚-----------------------------------------------redo: 用来重做(前滚)undo: 用来回滚(后滚) redo: 重做日志文件, 数据库的事务日志, online redo, archived log两类(都是磁盘文件)    如果数据库所在的机房掉电

Oracle架构实现原理(转)

Oracle RDBMS架构图 一般我们所说的Oracle指的是Oracle RDBMS(Relational databases Management system),一套Oracle数据库管理系统,也称之为Oracle Server.而Oracle Server主要有两大部分: Oracle Server = 实例 + 数据库 (Instance和Database是相互独立的) 数据库 = 数据文件 + 控制文件 +日志文件 实例 = 内存池 + 后台进程 所以可以细分为: Oracle S

Oracle架构实现原理、含五大进程解析

目录 目录 前言 Oracle的体系结构 Oracle RDBMS架构图 存储结构 物理结构 Data Files Redo Log Files Control Files Parameter File Password File 逻辑结构 逻辑空间到物理空间的映射 内存结构 系统全局区SGA 高速缓存缓冲区数据库缓冲区 日志缓冲区 共享池 大型池 JAVA池 进程结构 用户连接进程 程序全局区PGA 用户进程User Process Server Process服务进程 后台进程 数据库写入进