暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

深入解析oracle:中的回滚与撤销

原创 time 2022-10-11
2875

回滚与撤销
为了保证数据库中多个用户间的读一致性和能够回退事务,Oracle 必须拥有一种机制,能
够为变更的数据构造一种前镜像 (before image)数据(保存修改之前的旧值), 以保证能够回滚
或撤消对数据库所作的修改,同时为数据恢复以及一致性读服务。
这就是回滚(或撤消)。在上一章中我们ᨀ到 Redo,我们说 Redo 是用来保证在故障时事
务可以被恢复;那么 Undo 则是用来保证事务可以被回退或者撤销。
本章将着重介绍回滚(rollback)与撤销(undo)方面的知识。
8.1 什么是回滚和撤消
首先来介绍一下什么是回滚和撤消。我们知道,从 Oracle6 版本到 Oracle 9i 版本,Oracle
用数据库中的回滚段(Rollback)来ᨀ供撤消数据(Undo Data);而从 Oracle 9i 开始,Oracle
还ᨀ供了一种新的撤消数据(Undo Data)管理方式,就是使用 Oracle 自动管理的撤消(UNDO)
表空间(Automatic Undo Management,通常可以被缩写为 AUM)。
事务使用回滚段来记录变化前的数据或者撤销信息,回忆一下上一章中讲过的一个例子,
假定发出了一个更新语句:
UPDATE emp SET sal = 4000 Where empno= 7788;
下面看一下这个语句是怎样执行的(为了叙述方便,我们尽量简化了情况):
1. 检查 empno=7788 记录在 Buffer Cache 中是否存在,如果不存在则读取到 Buffer
Cache 中
2. 在回滚表空间的相应回滚段事务表上分配事务槽,这个操作需要记录 Redo 信息
3. 从回滚段读入或者在 Buffer Cache 中创建 sal=3000 的前镜像,这需要产生 Redo
信息并记入 Redo Log Buffer
4. 修改 Sal=4000,这是 update 的数据变更,需要记入 Redo Log Buffer。
5. 当用户ᨀ交时,会在 Redo Log Buffer 记录ᨀ交信息,并在回滚段标记该事务为非
激活(inactive)。
在以上事务处理过程中,注意 REDO 和 UNDO 是交替出现的,这两者对于数据库来说都
非常重要。在以上的步骤中,对于回滚段的操作存在多处,在事务开始时,首先需要在回滚表
空间获得一个事务槽,分配空间,然后创建前镜像,此后事务的修改才能进行,Oracle 必须以
此来保证事务是可以回退的。
如果用户ᨀ交(commit)了事务,Oracle 会在日志文件记录ᨀ交,并且写出日志,同时会
在回滚段中把该事务标记为已ᨀ交,ᨀ交事务在回滚段事务表的状态变为 INACIVE,然后该
事务所使用的回滚空间可以被重用,回滚段空间是循环使用的;如果用户回滚(rollback)事
务,则 Oracle 需要从回滚段中把前镜像数据读取出来,修改数据缓冲区,完成回滚,这个过
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·2·
程本身也要产生 Redo,所以回退这个操作是很昂贵的。
在 Oracle 的性能优化中,有一个性能指标称为平均事务回滚率(Rollback per transaction),
用来衡量数据库的ᨀ交与回滚效率。在 Statspack 的报告中,可以从开头部分找到这个指标:
Load Profile
~~~~~~~~~~~~
% Blocks changed per Read: 0.37 Recursive Call %: 1.14
Rollback per transaction %: 38.22 Rows per Sort: 11.83
该参数计算公式如下:
Round(User rollbacks / (user commits + user rollbacks) ,4)* 100%
其中 user commits 和 user rollbacks 数据来自系统的统计信息,可以从 V$SYSSTAT 视图
中得到,在 Statspack 中也包含着部分数据的输出,本例选择的报告相关部分摘录如下:
user commits 31,910 12.9 0.6
user rollbacks 19,740 8.0 0.4
按照公式计算就可以得出平均事务回滚率:
Round(19740 / (31910 + 19740),4) = .3822
这个指标应该接近于 0,如果该指标过高,则说明数据库的回滚过多。回滚过多不仅说明
数据库经历了太多的无效操作,而且这些操作会极大影响数据库性能。
8.2 回滚段存储的内容
在上一章中讲过,REDO 中只会记录少量信息,这些信息足以重演事务;同样 UNDO 中
也只记录精简信息,这些信息足以撤消事务。
对于 insert 操作,回滚段只需要记录插入记录的 rowid,如果回退,只需将该记录根据 rowid
删除即可;对于 update 操作,回滚段只需要记录被更新字段的旧值即可(前镜像),回退时通
过旧值覆盖新值即可完成回退;对于 delete 操作,Oracle 则必须记录整行的数据,在回退时,
Oracle 通过一个反向操作恢复删除的数据。
通过以上介绍可以简单总结一下:对于相同数据量的数据操作, 通常 insert 产生最少的
UNDO, update 产生的 UNDO 居中,而 delete 操作产生的 UNDO 最多。这也就是我们经常看到
的,当一个大的 Delete 操作失败或者回滚,总是需要很长的时间,并且会有大量的 redo 生成。
所以通常在进行大规模数据删除操作时,推荐通过分批删除分次ᨀ交,以减少对于回滚段的占
用和冲击。
回滚段在 UNDO 表空间中分配,其数据在 Buffer Cache 内存中的管理方式与用户数据一
致,同样按照相同的规则写出到 Undo 表空间的数据文件上。UNDO 表空间中的存储空间同样
按照 Segment 来分配和使用。下图显示的就是回滚段的机制与原理示意,回滚段的作用除了回
退事务外,还要参与事务恢复,以及ᨀ供读一致性。
因为 UNDO 数据要参与事务恢复,所以在备份数据库时一定要包含 UNDO 表空间,而且
一旦 UNDO 表空间损坏会丢失,那么数据库将会出现故障,需要进行介质恢复来恢复相关数
据文件:
第 1 章 章名章名章名章名章名
·3·
8.3 并发控制和一致性读
允许多用户并发访问是数据库必需满足的功能,那么怎样实现并发访问、控制和数据修改
就成为了一个重要问题。
一方面 Oracle 通过锁定机制实现数据库的并发控制;一方面通过多版本(Multi-versioning
Model)模型来进行并发数据访问。通过多版本架构,Oracle 实现了读取和写入的分离,使得
写入不阻塞读取;读取不阻塞修改。这是 Oracle 数据库区别于其他数据库的一个重要特征。
多版本模型在 Oracle 数据库中是通过一致性读来实现的,一致性读也正是回滚表空间的
主要作用之一。
Oracle 一方面不允许其他用户读取未ᨀ交数据,一方面要保证用户读取的数据要来自同一
时间点。让我们通过下图来看一下什么是 Oracle 的一致性读取(Consistent Reads):
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·4·
假定员工 SCOTT 的薪水为 3000:
在 T1 时间我们在 Session1 查询可以得到这个结果;
在 T2 时间 Session2 进行更新,将 SCOTT 的薪水增加 1000,并未ᨀ交(此时数据在内存
中已经修改,该 Buffer 状态变为 Dirty);
在 T3 时间 Session1 再次查询,注意此时,Oracle 不会允许其他用户看到未ᨀ交数据,所
以此时,Oracle 需要通过回滚段记录的前镜像进行一致性读,将 3000 恢复出来ᨀ供给用户,
这是一致性读的作用;
在 T4 时间,Session2 ᨀ交该更改,此时数据修改被永久化;
在 T5 时间,其他用户再次查询时,将会看到变化后的数据,也就是“4000”
在前文我们曾经ᨀ到,Oracle 内部使用 SCN 作为数据库时钟,这里查询结果集就是根据
SCN 来进行判断的,每个数据块头部都会记录一个ᨀ交 SCN,当数据更改ᨀ交后,ᨀ交 SCN
同时被修改,这个 SCN 在查询时可以用来进行一致性读判断。
在上图所示中,假定查询开始的时间为 T1,则在查询获取的数据块中,如果数据块的ᨀ
交 SCN 小于 T1,则 Oracle 接受该数据,如果ᨀ交 SCN 大于 T1 或者数据被锁定修改尚未记
录 COMMIT SCN,则 Oracle 需要通过回滚段构造前镜像来返回结果,这就是一致性读的本质
含义。
下图进一步体现了回滚段在一致性读中的重要作用:
第 1 章 章名章名章名章名章名
·5·
8.4 回滚段的前世今生
在 Oracle9i 之前,回滚表空间创建之后,Oracle 随后创建回滚段供数据库使用,也可以手
工创建或者删除回滚段进行维护,比如在开始事务之前,可以通过如下命令指定使用特定的回
滚段:
set transaction use rollback segment <rollback_segment_name>;
以下是从 Oracle8i 数据库创建日志摘录的部分信息,这就是 Oracle8i 的基本管理方式:
Sat Apr 24 16:27:23 2004
CREATE TABLESPACE RBS DATAFILE '/data1/oracle/oradata/8.1.7/rbs01.dbf' SIZE 256M REUSE
AUTOEXTEND ON NEXT 5120K MINIMUM EXTENT 512K
DEFAULT STORAGE ( INITIAL 512K NEXT 512K MINEXTENTS 8 MAXEXTENTS
UNLIMITED )
Completed: CREATE TABLESPACE RBS DATAFILE '/data1/oracle/oradata/8.1.7/rbs01.dbf'
....
Sat Apr 24 16:27:36 2004
CREATE PUBLIC ROLLBACK SEGMENT RBS0 TABLESPACE RBS STORAGE ( OPTIMAL
4096K )
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·6·
Completed: CREATE PUBLIC ROLLBACK SEGMENT RBS0 TABLESPACE RBS
Sat Apr 24 16:27:36 2004
CREATE PUBLIC ROLLBACK SEGMENT RBS1 TABLESPACE RBS STORAGE ( OPTIMAL 4096K )
Completed: CREATE PUBLIC ROLLBACK SEGMENT RBS1 TABLESPACE RBS
.....
Sat Apr 24 16:27:37 2004
CREATE PUBLIC ROLLBACK SEGMENT RBS11 TABLESPACE RBS STORAGE ( OPTIMAL 4096K )
Completed: CREATE PUBLIC ROLLBACK SEGMENT RBS11 TABLESPACE RB
Sat Apr 24 16:27:37 2004
ALTER ROLLBACK SEGMENT "RBS0" ONLINE
Completed: ALTER ROLLBACK SEGMENT "RBS0" ONLINE
Sat Apr 24 16:27:37 2004
ALTER ROLLBACK SEGMENT "RBS1" ONLINE
Completed: ALTER ROLLBACK SEGMENT "RBS1" ONLINE
.......
Sat Apr 24 16:27:37 2004
ALTER ROLLBACK SEGMENT "RBS10" ONLINE
Completed: ALTER ROLLBACK SEGMENT "RBS10" ONLINE
Sat Apr 24 16:27:37 2004
ALTER ROLLBACK SEGMENT "RBS11" ONLINE
Completed: ALTER ROLLBACK SEGMENT "RBS11" ONLINE
可以从数据库中查询这些回滚段的状态:
SQL> col segment_name for a10
SQL> select segment_name,tablespace_name,status from dba_rollback_segs;
SEGMENT_NA TABLESPACE_NAME STATUS
---------- ------------------------------ ----------------
SYSTEM SYSTEM ONLINE
RBS0 RBS ONLINE
RBS1 RBS ONLINE
………..
RBS10 RBS ONLINE
RBS11 RBS ONLINE
13 rows selected.
从 Oracle9i 开始,Oracle 引入了自动管理的 UNDO 表空间,如果选择使用自动的 UNDO
表空间的管理, 那么用户不再能够创建或删除回滚段,也不再需要为事务指定回滚段,这一
切将由 Oracle 自动进行。
SQL> select * from v$version where rownum <2;
BANNER
----------------------------------------------------------------
第 1 章 章名章名章名章名章名
·7·
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
SQL> show parameter undo
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
undo_management string AUTO
undo_retention integer 10800
undo_suppress_errors boolean FALSE
undo_tablespace string UNDOTBS1
伴随自动的 UNDO 管理功能的引入,Oracle 随之引入了几个新的初始化参数:
1.undo_management 用来定义数据库使用的回滚段是否使用自动管理模式。该参数有两
个可选项,AUTO 表示自动管理,MANUAL 表示手工管理。
2.undo_tablespace 用来定义在自动管理模式下,当前实例使用哪个 UNDO 表空间
3.undo_suppress_errors 表示当使用自动管理模式时,如果使用不再支持的操作时(比如
为事务指定回滚段)是否返回出错信息。设置为 True 时不返回出错信息,操作无效但是可以
继续,设置为 False 时,则操作不能继续,这实际上是一个向后兼容的参数。
SQL> set transaction use rollback segment rbs1;
set transaction use rollback segment rbs1
*
ERROR 位于第 1 行:
ORA-30019: 自动撤消模式中的回退段操作非法
SQL> show parameter undo_suppress_errors
NAME TYPE VALUE
------------------------------------ ----------- ---------------
undo_suppress_errors boolean FALSE
SQL> set transaction use rollback segment rbs1;
set transaction use rollback segment rbs1
*
ERROR 位于第 1 行:
ORA-30019: 自动撤消模式中的回退段操作非法
SQL> alter session set undo_suppress_errors=true;
会话已更改。
SQL> set transaction use rollback segment rbs1;
事务处理集。
该参数在 Oracle10g 中已经被舍弃:
SQL> select * from v$version where rownum <2;
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.1.0.2.0 - 64bi
SQL> show parameter undo
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·8·
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
undo_management string AUTO
undo_retention integer 100000
undo_tablespace string UNDOTBS1
4.undo_retention 表示在自动管理模式下,当回滚段变得非激活(INACTIVE)之后,回
滚段中的数据在被覆盖前保留的时间,该参数单位是秒。在 Oracle9iR2 中,这个参数的缺省
值为 10800 秒,也就是 3 个小时。通过该参数的调节作用,在繁忙的查询系统中,可以有效地
避免 ORA-01555 错误,这是 Oracle9i AUM 的一大增强。
在自动管理的 UNDO 表空间下,回滚段的个数是 Oracle 根据数据库的繁忙程度自动分配
或者回收的,缺省的,数据库创建时初始化 10 个回滚段:
Mon Apr 26 15:56:27 2004
CREATE UNDO TABLESPACE UNDOTBS1 DATAFILE '/opt/oracle9/oradata/testora9/undotbs01.dbf'
SIZE 200M REUSE AUTOEXTEND ON NEXT 5
120K MAXSIZE UNLIMITED
Mon Apr 26 15:56:33 2004
Mon Apr 26 15:56:33 2004
Created Undo Segment _SYSSMU1$
Created Undo Segment _SYSSMU2$
Created Undo Segment _SYSSMU3$
Created Undo Segment _SYSSMU4$
Created Undo Segment _SYSSMU5$
Created Undo Segment _SYSSMU6$
Created Undo Segment _SYSSMU7$
Created Undo Segment _SYSSMU8$
Created Undo Segment _SYSSMU9$
Created Undo Segment _SYSSMU10$
Undo Segment 1 Onlined
Undo Segment 2 Onlined
Undo Segment 3 Onlined
Undo Segment 4 Onlined
Undo Segment 5 Onlined
Undo Segment 6 Onlined
Undo Segment 7 Onlined
Undo Segment 8 Onlined
Undo Segment 9 Onlined
Undo Segment 10 Onlined
Successfully onlined Undo Tablespace 1.
从数据库内部 v$rollname 视图也可以查询得到这些自动创建的回滚段信息:
第 1 章 章名章名章名章名章名
·9·
SQL> select * from v$rollname;
USN NAME
---------- ------------------------------
0 SYSTEM
1 _SYSSMU1$
2 _SYSSMU2$
3 _SYSSMU3$
4 _SYSSMU4$
5 _SYSSMU5$
6 _SYSSMU6$
7 _SYSSMU7$
8 _SYSSMU8$
9 _SYSSMU9$
10 _SYSSMU10$
11 rows selected.
在系统繁忙时,可以从数据库的告警日志文件中看到回滚段的动态创建和释放过程:
Tue Mar 21 00:06:51 2006
Created Undo Segment _SYSSMU11$
Tue Mar 21 00:06:51 2006
Undo Segment 11 Onlined
Tue Mar 21 00:06:52 2006
Created Undo Segment _SYSSMU12$
Undo Segment 12 Onlined
Tue Mar 21 00:06:53 2006
Created Undo Segment _SYSSMU13$
Tue Mar 21 00:06:54 2006
Created Undo Segment _SYSSMU14$
Tue Mar 21 00:06:54 2006
Undo Segment 13 Onlined
Tue Mar 21 00:06:54 2006
Undo Segment 14 Onlined
。。。。。。。
Created Undo Segment _SYSSMU34$
Undo Segment 34 Onlined
。。。。。。。。
Tue Mar 21 03:47:39 2006
SMON offlining US=11
SMON offlining US=12
SMON offlining US=13
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·10·
SMON offlining US=14
………
SMON offlining US=33
SMON offlining US=34
动态创建和释放,这也正是自动管理的 UNDO 表空间的优势之一。
在 Oracle11g 中,UNDO 段的命名规则有了进一步变化,现在 Oracle 将回滚段创建的时间
戳包含在 回滚段名称中,这样我们通过名称就能得知一个 UNDO Segment 的创建时间,请看
以下查询:
SQL> select * from v$version where rownum <2;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - Production
SQL> select * from v$rollname;
USN NAME
---------- ------------------------------
0 SYSTEM
1 _SYSSMU1_1215150730$
2 _SYSSMU2_1215150730$
3 _SYSSMU3_1215150730$
4 _SYSSMU4_1215150730$
5 _SYSSMU5_1215150730$
6 _SYSSMU6_1215150730$
7 _SYSSMU7_1215150730$
8 _SYSSMU8_1215150730$
9 _SYSSMU9_1215150730$
10 _SYSSMU10_1215150730$
11 rows selected.
这里的时间戳是 Unix Time,需要经过转换才能变幻为标准时间。请看以下测试。首先创
建一个新的 UNDO 表空间 undotbs2,然后设置数据库切换到这个新建的 UNDO 表空间:
SQL> create undo tablespace undotbs2 datafile size 50M;
Tablespace created.
SQL> ALTER SYSTEM SET undo_tablespace='UNDOTBS2';
System altered.
原有 UNDO 表空间的回滚段会逐渐离线,新的表空间 UNDO 段顺序创建:
SQL> select * from v$rollname;
USN NAME
---------- ------------------------------
0 SYSTEM
3 _SYSSMU3_1215150730$
第 1 章 章名章名章名章名章名
·11·
11 _SYSSMU11_1217486802$
12 _SYSSMU12_1217486803$
13 _SYSSMU13_1217486803$
14 _SYSSMU14_1217486803$
15 _SYSSMU15_1217486803$
16 _SYSSMU16_1217486803$
17 _SYSSMU17_1217486803$
18 _SYSSMU18_1217486803$
19 _SYSSMU19_1217486803$
20 _SYSSMU20_1217486803$
12 rows selected.
在 Linux 上可以通过 Date 命令将这个 Unix Time 时间戳转换成标准时间(也可以在数据
库中自己编写一个函数进行转换):
[oracle@localhost trace]$ date -d '1970-01-01 UTC 1217486802 seconds' +"%Y-%m-%d %T %z"
2008-07-31 14:46:42 +0800
[oracle@localhost trace]$ date
Thu Jul 31 14:47:27 CST 2008
8.5 Oracle10g 的 UNDO_RETENTION 管理增强
在 AUM 模式下,我们知道 UNDO_RETENTION 参数用以控制事务ᨀ交以后 undo 信息保
留的时间。该参数以秒为单位,9iR1 初始值为 900 秒,在 Oracle9iR2 增加为 10800 秒。但是
这是一个 NO Guaranteed 的限制。也就是说,如果有其他事务需要回滚空间,而空间出现不足
时,这些信息仍然会被覆盖。
从 Oracle10g 开始,如果你设置 UNDO_RETENTION 为 0,那么 Oracle 启用自动调整以
满足最长运行查询的需要。当然如果空间不足,那么 Oracle 满足最大允许的长时间查询。而
不再需要用户手工调整。当设置 undo_retention 为 0 后,在告警日志文件中可以看到如下信息:
Autotune of undo retention is turned on.
这个新特性的引入伴随着几个新的隐含初始化参数,主要的参数有 2 个:
SQL> select ksppinm,ksppdesc
2 from x$ksppi where ksppinm like '%&var%';
Enter value for var: undo_autotune
old 2: from x$ksppi where ksppinm like '%&var%'
new 2: from x$ksppi where ksppinm like '%undo_autotune%'
KSPPINM KSPPDESC
------------------------------ -------------------------------------------------------
_undo_autotune enable auto tuning of undo_retention
SQL> /
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·12·
Enter value for var: collect_undo_stats
old 2: from x$ksppi where ksppinm like '%&var%'
new 2: from x$ksppi where ksppinm like '%collect_undo_stats%'
KSPPINM KSPPDESC
------------------------------ -------------------------------------------------------
_collect_undo_stats Collect Statistics v$undostat
这两个参数缺省都是打开的。
很多时候我们希望前镜像数据能够尽量,而不是被覆盖,Oracle10g 对于 UNDO 增加了
Guarantee 控制,也就是说,你可以指定 UNDO 表空间必须满足 UNDO_RETENTION 的限制。
通过如下命令可以修改 UNDO 表空间的新属性:
alter tablespace undotbs1 retention guarantee|noguarantee;
设置期望的保留时间,修改 UNDO 表空间属性,就可以使 UNDO 表空间运行在 Guarantee
模式:
SQL> alter system set undo_retention=900;
System altered.
SQL> alter tablespace undotbs1 retention guarantee;
Tablespace altered.
SQL> select tablespace_name,contents,retention from dba_tablespaces
2 where tablespace_name like 'UNDO%';
TABLESPACE_NAME CONTENTS RETENTION
------------------------------ --------- -----------
UNDOTBS1 UNDO GUARANTEE
将 UNDO 表空间自动扩展属性取消进行如下测试:
SQL> select bytes/1024/1024 from v$datafile where name like '%undo%';
BYTES/1024/1024
---------------
50
SQL> alter database datafile '/opt/oracle/oradata/eygle/undotbs01.dbf' autoextend off;
Database altered.
尝试循环小批量删除数据,在 GURANTEE 设置下,很快出现 ORA-30036 错误:
SQL> connect eygle/eygle
Connected.
SQL> select count(*) from t;
COUNT(*)
----------
1298100
SQL> begin
2 for i in 1 .. 1000 loop
3 delete from t where rownum <1001;
第 1 章 章名章名章名章名章名
·13·
4 commit;
5 end loop;
6 end;
7 /
begin
*
ERROR at line 1:
ORA-30036: unable to extend segment by 8 in undo tablespace 'UNDOTBS1'
ORA-06512: at line 3
SQL> select count(*) from t;
COUNT(*)
----------
1294100
而在修改了 UNDO 表空间 retention 属性后,删除可以顺利完成:
SQL> alter tablespace undotbs1 retention noguarantee;
Tablespace altered.
SQL> begin
2 for i in 1 .. 1000 loop
3 delete from t where rownum <1001;
4 commit;
5 end loop;
6 end;
7 /
PL/SQL procedure successfully completed.
这就是 GUARANTEE 与 NOGUARANTEE 的不同。
8.6 UNDO_RETENTION 的内部实现
UNDO_RETENTION 机制从 Oracle9i 开始引入,为了实现这一机制,Oracle 在 Undo
Segment Header 上创建了一个 Retention Table 用于记录相关 UNDO 存储的ᨀ交时间,从而实
现其保留策略。
接下来让我们一起来看一下这个 Retention Table 的内容,以下测试来自于 Oracle11g 数据
库环境。
SQL> select banner from x$version where indx=3;
BANNER
--------------------------------------------------------------------------------
TNS for Linux: Version 11.1.0.6.0 - Production
首先使用测试用户执行一个 DML 事务,删除测试表中的部分数据:
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·14·
SQL> connect eygle/eygle
Connected.
SQL> delete from eygle where rownum <10;
9 rows deleted.
由于这是一个测试数据库,没有其他事务进行,所以可以通过以下查询找到当前事务使
用的回滚段:
SQL> select a.usn,a.xacts,b.name from v$rollstat a,v$rollname b
2 where a.usn=b.usn and a.xacts >0;
USN XACTS NAME
---------- ---------- ------------------------------
15 1 _SYSSMU15_1217486803$
使用如下命令将 UNDO HEADER 转储出来:
SQL> alter system dump undo header '_SYSSMU15_1217486803$';
System altered.
然后ᨀ交这个事务:
SQL> commit;
Commit complete.
接下来再启动一个新的会话连接,再次执行一次回滚段头的转储输出:
[oracle@localhost trace]$ sqlplus "/ as sysdba"
SQL> alter system dump undo header '_SYSSMU15_1217486803$';
System altered.
找到两次转储生成的跟踪文件:
[oracle@localhost trace]$ ls -sort|tail -4
8 -rw-r----- 1 oracle 60 Jul 31 16:00 11gtest_ora_30455.trm
16 -rw-r----- 1 oracle 8924 Jul 31 16:00 11gtest_ora_30455.trc
8 -rw-r----- 1 oracle 70 Jul 31 16:01 11gtest_ora_30492.trm
16 -rw-r----- 1 oracle 9033 Jul 31 16:01 11gtest_ora_30492.trc
先进行简单的 diff 差异比较,以下摘录出来的信息就是来自保留表中的数据:
[oracle@localhost trace]$ diff 11gtest_ora_30455.trc 11gtest_ora_30492.trc
45c46,47
< Extent Number:1 Commit Time: 1217486940
---
> Extent Number:1 Commit Time: 1217491248
> Extent Number:2 Commit Time: 0
注意ᨀ交后,回滚段区间(Extent)的ᨀ交时间发生改变,后者的时间正是刚才的ᨀ交时
间:
[oracle@localhost trace]$ date
Thu Jul 31 16:01:54 CST 2008
[oracle@localhost trace]$ date -d '1970-01-01 UTC 1217491248 seconds' +"%Y-%m-%d %T %z"
第 1 章 章名章名章名章名章名
·15·
2008-07-31 16:00:48 +0800
最后让从转储文件中摘录一个更为完整的保留表信息供参考(ᨀ交后的信息输出):
Retention Table
-----------------------------------------------------------
Extent Number:0 Commit Time: 1217486940
Extent Number:1 Commit Time: 1217491248
Extent Number:2 Commit Time: 0
8.7 Oracle10g In Memory Undo 新特性
通过以前的介绍我们知道 UNDO 的管理方式和常规的数据管理方式是相同的,当进行数
据修改时,会在 Buffer 中创建前镜像,同时会记录相应的 REDO,然后这些 UNDO 数据同样
会写出到 UNDO SEGMENT 上,当进行一致性读或回滚时,可能会产生大量的 Consistent Gets
和 physical reads。注意到这里,UNDO 会产生 REDO 信息,又会写 UNDO SEGMENT,进而
又可能产生大量读取 I/O,这些都是资源密集型操作。如果能够缩减 UNDO 在这些环节中的
REDO 与 UNDO 写出,那么显然就可以极大的ᨀ升数据库性能,减少资源的消耗和使用。
从 Oracle10g 开始,Oracle 在数据库中引入了 In Memory UNDO(可以被缩写为 IMU)的
新技术,使用这一技术,数据库会在共享内存中(Shared Pool)开辟独立的内存区域用于存储
UNDO 信息,这样就可以避免 UNDO 信息以前在 Buffer Cache 中的读写操作,从而可以进一
步的减少 Redo 生成,同时可以大大减少以前的 UNDO Segment 的操作。IMU 中数据通过暂存、
整理与收缩之后也可以写出到回滚段,这样的写出ᨀ供了有序、批量写的性能ᨀ升。
IMU 机制与上一章ᨀ到的 PVRS 紧密相关,由于每个 IMU Buffer 的大小在 64~128k 左
右,所以仅有特定的小事务可以使用,每个事务会被绑定到一个独立空闲的 IMU Buffer,同
时相关的 Redo 信息会写入 PVRS 中,同样每个 IMU Buffer 会由一个独立的 In Memory Undo
Latch 保护,当 IMU Buffer 或 PRVS 写满之后,数据库需要写出 IMU 中的信息。
一个新引入的隐含参数可以控制该特性是否启用,这个参数是_in_memory_undo,在
Oracle10g 中这个参数的缺省值是 TRUE(不同版本和平台参数的初始设置可能不同):
SQL> @GetHidPar
Enter value for par: in_memo
old 4: AND x.ksppinm LIKE '%&par%'
new 4: AND x.ksppinm LIKE '%in_memo%'
NAME VALUE PDESC
------------------------------ ---------- ----------------------------------------------
_in_memory_undo TRUE Make in memory undo for top level transactions
IMU 的内存在 Shared Pool 中分配,回想一下 Redo Log Buffer 的内存使用与功能,实际上
IMU 技术在某种程度上也是参考了 Log Buffer 的机制,通过以下查询可以获得系统当前分配
的 IMU 内存:
SQL> select * from v$sgastat where name ='KTI-UNDO';
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·16·
POOL NAME BYTES
------------ ------------------------------ ----------
shared pool KTI-UNDO 1235304
In Memory Undo 池缺省的会分配三个,用以ᨀ供更好的并发:
SQL> @GetHidPar
Enter value for par: imu_pool
old 4: AND x.ksppinm LIKE '%&par%'
new 4: AND x.ksppinm LIKE '%imu_pool%'
NAME VALUE PDESC
------------------------------ ---------- ----------------------------------
_imu_pools 3 in memory undo pools
IMU 的使用信息,如ᨀ交次数可以通过 V$SYSSTAT 视图查询:
SQL> select name,value from v$sysstat where name like '%commits';
NAME VALUE
------------------------------ -----------------
user commits 73980
IMU commits 57548
新的内存 Buffer 通过 In Memory Undo Latch 来进行保护:
SQL> select name,gets,misses,immediate_gets,sleeps
2 from v$latch_children where name like '%undo latch';
NAME GETS MISSES IMMEDIATE_GETS SLEEPS
------------------------------ ---------- ---------- -------------- ----------
In memory undo latch 54128 0 20253 0
In memory undo latch 303069 3 56878 2
In memory undo latch 135677 1 39360 0
In memory undo latch 118217 16 35919 2
In memory undo latch 110777 0 39421 0
In memory undo latch 192209 31 20977 0
In memory undo latch 234610 1 87358 0
In memory undo latch 61835 0 34315 0
In memory undo latch 3 0 0 0
In memory undo latch 3 0 0 0
In memory undo latch 3 0 0 0
In memory undo latch 3 0 0 0
In memory undo latch 3 0 0 0
In memory undo latch 3 0 0 0
In memory undo latch 3 0 0 0
In memory undo latch 3 0 0 0
In memory undo latch 3 0 0 0
第 1 章 章名章名章名章名章名
·17·
In memory undo latch 3 0 0 0
18 rows selected.
除了前面ᨀ到的,还有几个隐含参数与 IMU 有关:
u _recursive_imu_transactions 控制递归事务是否使用 IMU,该参数缺省值为 False
u _db_writer_flush_imu 控制是否允许 DBWR 将 IMU 事务的降级为常规事务,并执行
UNDO SEGMENT 的写出操作,缺省值为 TRUE
此外,在 RAC 环境中,IMU 不被支持。
经过不同版本Oracle技术的不断演进,Oracle的内存管理已经和以前大为不同,现在Buffer
Cache、Shared Pool、Log Buffer 的内容正在不断交换渗透,Redo、UNDO 数据都可以部分的
存储在共享池中,Oracle11g 的 Result Cache 也被记录在 Shared Pool 当中。
回忆一下前几章᧿述过的变化,现在 Oracle 的实例结构图如下显示则更为确切:
8.8 Oracle11g UNDO 表空间备份增强
前面ᨀ到,由于 UNDO 表空间在恢复时不可缺少,所以在进行备份时必须备份该表空间,
但是我们知道一旦事务ᨀ交,修改被确认,则该事务的前镜像被标记为 INACTIVE,其中的信
息在恢复时也就不会被用到,如果在备份时能够跳过这些数据,则备份 UNDO 表空间的效率
就可以大大ᨀ高。
在 Oracle Database 11g 中,Oracle 引入了一个新的特性 RMAN UNDO 备份优化。在 RMAN
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·18·
备份 UNDO 表空间时,ᨀ交事务的 UNDO 信息将不再备份,这个特性随 RMAN 强制启用。
在测试中,一个数 G 的 Undo 表空间备份文件的大小仅为数百 K:
RMAN> list backup;
List of Backup Sets
===================
BS Key Type LV Size Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
1 Full 352.00K DISK 00:00:10 14-JUL-08
BP Key: 1 Status: AVAILABLE Compressed: NO Tag: TAG20080714T164554
Piece Name: /backupset/2008_07_14/o1_mf_nnndf_TAG20080714T164554_47p4ldcm_.bkp
List of Datafiles in backup set 1
File LV Type Ckp SCN Ckp Time Name
---- -- ---- ---------- --------- ----
3 Full 1045063 14-JUL-08 /data1/oradata/11gtest/11gtest/undotbs01.dbf
这一特性是许多 DBA 期待已久的,在一个繁忙的生产环境中,UNDO 表空间可能占用几
十 G 的空间,全部备份显然并不合理,现在 Oracle11g 解决了这个问题。
8.9 回滚机制的深入研究
如果大家有兴趣深入了解一下回滚段的机制,那么请跟随我将前面的例子进一步深化。
1. 从 DML 更新事务开始
我们重新来看这个更新语句:
SQL> connect scott/tiger
Connected.
SQL> select * from emp;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 19-APR-87 3000 20
。。。。。。
14 rows selected.
第 1 章 章名章名章名章名章名
·19·
SQL> UPDATE emp SET sal = 4000 Where empno= 7788;
1 row updated.
SQL> select * from emp where empno=7788;
EMPNO ENAME JOB MGR HIREDATE SAL COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7788 SCOTT ANALYST 7566 19-APR-87 4000 20
先不ᨀ交这个事务,在另外窗口新开 Session,使用 SYS 用户查询相关信息,进行进一步
的分析研究。
2. 获得事务信息
从事务表中我们可以获得关于这个事务的信息,该事务位于 6 号回滚段(XIDUSN),在
6 号回滚段上,该事务位于第 23 号事务槽(XIDSLOT):
SQL> SELECT xidusn, xidslot, xidsqn, ubablk, ubafil, ubarec FROM v$transaction;
XIDUSN XIDSLOT XIDSQN UBABLK UBAFIL UBAREC
---------- ---------- ---------- ---------- ---------- ----------
6 23 14030 85 2 63
从 V$ROLLSTAT 视图中也可以获得事务信息,XACTS 字段代表的是活动事务的数量,
同样我们看到该事务位于 6 号回滚段:
SQL> select usn,writes,rssize,xacts,hwmsize,shrinks,wraps from v$rollstat;
USN WRITES RSSIZE XACTS HWMSIZE SHRINKS WRAPS
---------- ---------- ---------- ---------- ---------- ---------- ----------
0 4680 385024 0 385024 0 0
1 20674 1171456 0 1171456 0 0
2 29240 1171456 0 1171456 0 0
3 31652 1171456 0 1171456 0 0
4 22968 1171456 0 1171456 0 0
5 30406 1171456 0 1171456 0 0
6 31282 1171456 1 1171456 0 0
7 21510 1171456 0 1171456 0 0
8 28472 1171456 0 1171456 0 1
9 69830 1171456 0 1171456 0 0
10 23328 1171456 0 1171456 0 0
11 rows selected.
3. 获得回滚段名称并转储段头信息
查询 V$ROLLNAME 视图获得回滚段名称,并转储回滚段头信息:
SQL> select * from v$rollname a where a.usn=6;
USN NAME
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·20·
---------- ------------------------------
6 _SYSSMU6$
SQL> alter system dump undo header '_SYSSMU6$';
System altered
生成的跟踪文件如下:
SQL> @gettrcname
TRACE_FILE_NAME
-----------------------------------------------------------------------
/opt/oracle/admin/conner/udump/conner_ora_15309.trc
4. 获得跟踪文件信息
注意这就是我们前边多次ᨀ到过的回滚段头的信息,其中包括事务表信息,从以下的跟
踪文件中,可以清晰的看到这些内容:
********************************************************************************
Undo Segment: _SYSSMU6$ (6)
********************************************************************************
Extent Control Header
-----------------------------------------------------------------
Extent Header:: spare1: 0 spare2: 0 #extents: 2 #blocks: 15
last map 0x00000000 #maps: 0 offset: 4080
Highwater:: 0x00800055 ext#: 1 blk#: 4 ext size: 8
#blocks in seg. hdr's freelists: 0
#blocks below: 0
mapblk 0x00000000 offset: 1
Unlocked
Map Header:: next 0x00000000 #extents: 2 obj#: 0 flag: 0x40000000
Extent Map
-----------------------------------------------------------------
0x0080004a length: 7
0x00800051 length: 8

Retention Table
-----------------------------------------------------------
Extent Number:0 Commit Time: 1144779956
Extent Number:1 Commit Time: 1143540568

TRN CTL:: seq: 0x02de chd: 0x0025 ctl: 0x0023 inc: 0x00000000 nfb: 0x0000
mgc: 0x8201 xts: 0x0068 flg: 0x0001 opt: 2147483646 (0x7ffffffe)
uba: 0x00800055.02de.3d scn: 0x0819.003f5d3e
Version: 0x01
第 1 章 章名章名章名章名章名
·21·
FREE BLOCK POOL::
uba: 0x00000000.02de.3c ext: 0x1 spc: 0x30a
uba: 0x00000000.02de.22 ext: 0x1 spc: 0x144e
uba: 0x00000000.02d8.18 ext: 0x6 spc: 0x1206
uba: 0x00000000.0000.00 ext: 0x0 spc: 0x0
uba: 0x00000000.0000.00 ext: 0x0 spc: 0x0
TRN TBL::
index state cflags wrap# uel scn dba parent-xid
--------------------------------------------------------------------------------------
<为了排版关系,省略了一些条目,并且截去了最后一个列的信息,完整内容可以从作者的网站得到>
0x14 9 0x00 0x36ce 0x0016 0x0819.00402706 0x00800055 0x0000.000.00000000
0x15 9 0x00 0x36ce 0x001b 0x0819.004036d1 0x00800055 0x0000.000.00000000
0x16 9 0x00 0x36ce 0x0019 0x0819.00402af9 0x00800055 0x0000.000.00000000
0x17 10 0x80 0x36ce 0x0001 0x0819.004066aa 0x00800055 0x0000.000.00000000
0x18 9 0x00 0x36ce 0x0015 0x0819.004032df 0x00800055 0x0000.000.00000000
0x19 9 0x00 0x36ce 0x0018 0x0819.00402eec 0x00800055 0x0000.000.00000000
0x1a 9 0x00 0x36ce 0x001c 0x0819.00403eb9 0x00800055 0x0000.000.00000000
............
回顾前面的事务信息,该事务正好占用的是第 23 号事务槽(0x17),状态(State)为 10 代
表的是活动事务。
5. 转储前镜像信息
我们再来看 DBA(Data Block Address),这个 DBA 指向的就是包含这个事务的前镜像的
数据块地址: 0x00800055 。
我们看一下这个地址如何换算:
DBA 代表数据块的存储地址,由 10 位文件号 + 22 位数据块(Block)组成。
我们将 0x00800055 转换为 2 进制就是:
0000 0000 1000 0000 0000 0000 0101 0101
前 10 位代表文件号为 2,后 22 位代表 Block 号为 85。经过转换后,该前镜像信息位于 file
2 block 85。
这和我们从事务表中查询得到的数据完全一致:
SQL> SELECT xidusn, xidslot, xidsqn, ubablk, ubafil, ubarec FROM v$transaction;
XIDUSN XIDSLOT XIDSQN UBABLK UBAFIL UBAREC
---------- ---------- ---------- ---------- ---------- ----------
6 23 14030 85 2 63
提示:很多内容深入研究的内容在数据库内部都有完整的体现,不过通常我们很少注意,只有将
两者结合起来学习、研究和理解,我们才能深刻的理解到 Oracle 的本质。希望大家在阅读
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·22·
这部分内容的时候能够耐心、细致,能够有所收获。
为了同时说明一些其他内容,我们继续先前 Scott 用户的事务,再更新 2 条记录:
SQL> update emp set sal=4000 where empno=7788;
1 row updated.
SQL> update emp set sal=4000 where empno=7782;
1 row updated.
SQL> update emp set sal=4000 where empno=7698;
1 row updated.
然后将回滚段中的这个 Block 转储出来:
SQL> alter system dump datafile 2 block 85;
System altered
这是跟踪文件开始部分的信息:
Start dump data blocks tsn: 1 file#: 2 minblk 85 maxblk 85
buffer tsn: 1 rdba: 0x00800055 (2/85)
scn: 0x0819.004066c8 seq: 0x01 flg: 0x00 tail: 0x66c80201
frmt: 0x02 chkval: 0x0000 type: 0x02=KTU UNDO BLOCK
********************************************************************************
UNDO BLK:
xid: 0x0006.018.000036ce seq: 0x2de cnt: 0x3f irb: 0x3f icl: 0x0 flg: 0x0000
注意,这部分信息中有一个参数: irb:0x3f,irb 指的是回滚段中记录的最近的未ᨀ交变更
开始之处,如果开始回滚,这是起始的搜索点。
接下来是回滚信息的偏移量,最后一个偏移地址正是 0x3f 的信息:
Rec Offset Rec Offset Rec Offset Rec Offset Rec Offset
---------------------------------------------------------------------------
0x01 0x1f98 0x02 0x1f54 0x03 0x1efc 0x04 0x1ea4 0x05 0x1e54
0x06 0x1e10 0x07 0x1dbc 0x08 0x1d64 0x09 0x1d14 0x0a 0x1cd0
0x0b 0x1c74 0x0c 0x1c1c 0x0d 0x1bcc 0x0e 0x1b88 0x0f 0x1b30
0x10 0x1ad8 0x11 0x1a88 0x12 0x1a44 0x13 0x19f0 0x14 0x1998
0x15 0x1948 0x16 0x1904 0x17 0x18ac 0x18 0x1854 0x19 0x1804
0x1a 0x17c0 0x1b 0x1768 0x1c 0x1710 0x1d 0x16c0 0x1e 0x167c
0x1f 0x1624 0x20 0x15cc 0x21 0x157c 0x22 0x14a4 0x23 0x13fc
0x24 0x1354 0x25 0x12ac 0x26 0x1204 0x27 0x115c 0x28 0x10b4
0x29 0x100c 0x2a 0x0f64 0x2b 0x0ebc 0x2c 0x0e14 0x2d 0x0d6c
0x2e 0x0cc4 0x2f 0x0c1c 0x30 0x0b74 0x31 0x0acc 0x32 0x0a24
0x33 0x097c 0x34 0x08d4 0x35 0x082c 0x36 0x0784 0x37 0x06dc
0x38 0x0634 0x39 0x058c 0x3a 0x04e4 0x3b 0x043c 0x3c 0x0394
0x3d 0x0310 0x3e 0x02b4 0x3f 0x0258
*-----------------------------
第 1 章 章名章名章名章名章名
·23·
从接下来的信息中找到 0x3f 信息:
*-----------------------------
* Rec #0x3f slt: 0x17 objn: 7961(0x00001f19) objd: 7961 tblspc: 0(0x00000000)
* Layer: 11 (Row) opc: 1 rci 0x3e
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
op: C uba: 0x00800055.02de.3e
KDO Op code: URP row dependencies Disabled
xtype: XA bdba: 0x00405c5a hdba: 0x00405c59
itli: 2 ispac: 0 maxfr: 4863
tabn: 0 slot: 5(0x5) flag: 0x2c lock: 0 ckix: 0
ncol: 8 nnew: 1 size: 1
col 5: [ 3] c2 1d 33
c2 1d 33 转换为 10 进制就是: 2850 (关于数字值的内部存储及转换方式请参考本章末相
关部分)。
这是我们最后更新记录的前镜像,Oracle 就是这样通过回滚段保留前镜像信息的:
update emp set sal=4000 where empno=7698;
我们注意,在这条 UNDO 记录上,还记录一个数据:rci ,该参数代表的就是 undo chain(同
一事务中的多次修改,根据 Chain 链接关联)的下一个偏移量,此处为 0x3e.
我们找到 0x3e 这条 UNDO 记录:
*-----------------------------
* Rec #0x3e slt: 0x17 objn: 7961(0x00001f19) objd: 7961 tblspc: 0(0x00000000)
* Layer: 11 (Row) opc: 1 rci 0x3d
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
op: C uba: 0x00800055.02de.3d
KDO Op code: URP row dependencies Disabled
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·24·
xtype: XA bdba: 0x00405c5a hdba: 0x00405c59
itli: 2 ispac: 0 maxfr: 4863
tabn: 0 slot: 6(0x6) flag: 0x2c lock: 0 ckix: 0
ncol: 8 nnew: 1 size: 1
col 5: [ 3] c2 19 33
这里记录的 c2 19 33 转换为 10 进制就是:2450,是我们第二条更新的数据:
update emp set sal=4000 where empno=7782;
这里的 rci 指向下一条记录 0x3d。找到 0x3d:
*-----------------------------
* Rec #0x3d slt: 0x17 objn: 7961(0x00001f19) objd: 7961 tblspc: 0(0x00000000)
* Layer: 11 (Row) opc: 1 rci 0x00
Undo type: Regular undo Begin trans Last buffer split: No
Temp Object: No
Tablespace Undo: No
rdba: 0x00000000
*-----------------------------
uba: 0x00800055.02de.3c ctl max scn: 0x0819.003f594b prv tx scn: 0x0819.003f5d3e
KDO undo record:
KTB Redo
op: 0x04 ver: 0x01
op: L itl: xid: 0x0002.01d.000038ea uba: 0x008000c3.04b1.0c
flg: C--- lkc: 0 scn: 0x0819.0036f14f
KDO Op code: URP row dependencies Disabled
xtype: XA bdba: 0x00405c5a hdba: 0x00405c59
itli: 2 ispac: 0 maxfr: 4863
tabn: 0 slot: 7(0x7) flag: 0x2c lock: 0 ckix: 0
ncol: 8 nnew: 1 size: 0
col 5: [ 2] c2 1f
这里 c2 1f 转换为 10 进制是:3000,正是我们第一条更新的记录:
update emp set sal=4000 where empno=7788;
这是这个事务中最后一条更新的数据,所以其 undo chain 的指针为 0x00,表示这是最后
一条记录。
我们也可以从 x$bh 中找到这些数据块:
SQL> select b.segment_name,a.file#,a.dbarfil,a.dbablk,a.class,a.state
2 from x$bh a,dba_extents b where b.RELATIVE_FNO = a.dbarfil
3 and b.BLOCK_ID <= a.dbablk and b.block_id + b.blocks > a.dbablk
4 and b.owner='SCOTT' and b.segment_name='EMP';
SEGMENT_NAME FILE# DBARFIL DBABLK CLASS STATE
-------------------- ---------- -------- ---------- ---------- ------
第 1 章 章名章名章名章名章名
·25·
EMP 1 1 23641 4 1
EMP 1 1 23642 1 1
我们注意 class 为 4 的是段头,class 为 1、块号为 23642 的为数据块。
如果此时在其他进程查询 scott.emp 表,Oracle 需要构造一致性读,通过前镜像把变化前
的数据展现给用户:
SQL> select * from scott.emp;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
7369 SMITH CLERK 7902 1980-12-17 800.00 20
7499 ALLEN SALESMAN 7698 1981-2-20 1600.00 300.00 30
7521 WARD SALESMAN 7698 1981-2-22 1250.00 500.00 30
7566 JONES MANAGER 7839 1981-4-2 2975.00 20
7654 MARTIN SALESMAN 7698 1981-9-28 1250.00 1400.00 30
7698 BLAKE MANAGER 7839 1981-5-1 2850.00 30
7782 CLARK MANAGER 7839 1981-6-9 2450.00 10
7788 SCOTT ANALYST 7566 1987-4-19 3000.00 20
7839 KING PRESIDENT 1981-11-17 5000.00 10
7844 TURNER SALESMAN 7698 1981-9-8 1500.00 0.00 30
7876 ADAMS CLERK 7788 1987-5-23 1100.00 20
7900 JAMES CLERK 7698 1981-12-3 950.00 30
7902 FORD ANALYST 7566 1981-12-3 3000.00 20
7934 MILLER CLERK 7782 1982-1-23 1300.00 10
14 rows selected
我们再来查询:
SQL> select b.segment_name,a.file#,a.dbarfil,a.dbablk,a.class,a.state,
2 decode(bitand(flag,1), 0, 'N', 'Y') DIRTY
3 from x$bh a,dba_extents b where b.RELATIVE_FNO = a.dbarfil
4 and b.BLOCK_ID <= a.dbablk and b.block_id + b.blocks > a.dbablk
5 and b.owner='SCOTT' and b.segment_name='EMP';
SEGMENT_NAME FILE# DBARFIL DBABLK CLASS STATE
DIRTY
-------------------- ---------- ---------- ----------- ---------- ---------- -----
EMP 1 1 23641 4 1 N
EMP 1 1 23642 1 3 Y
EMP 1 1 23642 1 1 N
注意到此时,Buffer Cache 中多出一个数据块,也就是 23642 存在 2 份,其中 state 为 3
的就是一致性读构造的前镜像。
6. 转储数据块信息
在前镜像信息中,Oracle 还记录了前镜像对应的数据块的地址,我们可以从 bdba 记录中
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·26·
获得这部分信息,以先前的一个数据为例:
bdba: 0x00405c5a 记录了更改的数据块的地址,0x00405c5a 经过转换为 2 进制就是:
0000 0000 0100 0000 0101 1100 0101 1010
也正是 file 1 block 23642。
我们再将数据表中的 Block 转储出来,看看其中记录了什么样的信息:
SQL> alter system dump datafile 1 block 23642;
System altered.
检查跟踪文件,获取数据块信息:
Start dump data blocks tsn: 0 file#: 1 minblk 23642 maxblk 23642
buffer tsn: 0 rdba: 0x00405c5a (1/23642)
scn: 0x0819.00406ddf seq: 0x01 flg: 0x04 tail: 0x6ddf0601
frmt: 0x02 chkval: 0x6b6a type: 0x06=trans data
Block header dump: 0x00405c5a
Object id on Block? Y
seg/obj: 0x1f19 csc: 0x819.406ddf itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0009.02e.000036b0 0x00800081.0302.11 C--- 0 scn 0x0819.0036f205
0x02 0x0006.018.000036ce 0x00800055.02de.3f ---- 3 fsc 0x0002.00000000
这里存在 ITL 事务槽信息,ITL 事务槽指 Interested Transaction List(ITL),事务必须获得
一个 ITL 事务槽才能够进行数据修改。
ITL 内容主要包括:
xid---Transaction ID
Uba---Undo Block Address
Lck---Lock Status
xid=Undo.Segment.Number+Transaction.Table.Slot.Number+Wrap
在以上输出中,我们看到 itl2 (0x02)上存在活动事务。
我们将 xid= 0x0006.018.000036ce 分解一下:
该事务指向 6 号回滚段,Slot 号为 0x17(转换为 10 进制正好是 23),Wrap#为 36ce,正是
我们 dump 回滚段看到的那个事务。
index state cflags wrap# uel scn dba parent-xid nub
stmt_num
-----------------------------------------------------------------------------------------------
......
0x17 10 0x80 0x36ce 0x0001 0x0819.004066aa 0x00800055 0x0000.000.00000000
0x00000001 0x00000000
我们看到,在数据块上同样存在指向回滚段的事务信息。
Uba 代表的是 Undo Block Address , 指向具体的回滚段 , 我们看到 该 ITL 上
第 1 章 章名章名章名章名章名
·27·
uba=0x00800055.02de.3f
我们将这个 UBA 进行分解:
0x00800055 正是前镜像的地址,
seq: 02de 是顺序号
3f 是 UNDO 记录的开始地址(irb 信息)
UBA 的内容和 UNDO 中的信息完全相符:
UNDO BLK:
xid: 0x0006.018.000036ce seq: 0x2de cnt: 0x3f irb: 0x3f icl: 0x0 flg: 0x0000
继续向下我们可以找到这三条被修改的记录,锁定位信息 LB 指向 0x2 号 ITL 事务槽:
tab 0, row 5, @0x1d19
tl: 40 fb: --H-FL-- lb: 0x2 cc: 8
col 0: [ 3] c2 4d 63
col 1: [ 5] 42 4c 41 4b 45
col 2: [ 7] 4d 41 4e 41 47 45 52
col 3: [ 3] c2 4f 28
col 4: [ 7] 77 b5 05 01 01 01 01
col 5: [ 2] c2 29
col 6: *NULL*
col 7: [ 2] c1 1f
tab 0, row 6, @0x1d41
tl: 40 fb: --H-FL-- lb: 0x2 cc: 8
col 0: [ 3] c2 4e 53
col 1: [ 5] 43 4c 41 52 4b
col 2: [ 7] 4d 41 4e 41 47 45 52
col 3: [ 3] c2 4f 28
col 4: [ 7] 77 b5 06 09 01 01 01
col 5: [ 2] c2 29
col 6: *NULL*
col 7: [ 2] c1 0b
tab 0, row 7, @0x1e54
tl: 40 fb: --H-FL-- lb: 0x2 cc: 8
col 0: [ 3] c2 4e 59
col 1: [ 5] 53 43 4f 54 54
col 2: [ 7] 41 4e 41 4c 59 53 54
col 3: [ 3] c2 4c 43
col 4: [ 7] 77 bb 04 13 01 01 01
col 5: [ 2] c2 29
col 6: *NULL*
col 7: [ 2] c1 15
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·28·
至此,整个事务过程被完全解析。最后让我们通过一个图表清晰的看一下这个事务的内
部流程:
1. 首先当一个事务开始时,需要在回滚段事务表上分配一个事务槽
2. 在数据块头部获取一个 ITL 事务槽,该事务槽指向回滚段头的事务槽
3. 在修改数据之前,需要记录前镜像信息,这个信息以 UNDO RECORD 的形式存储在
回滚段中,回滚段头事务槽指向该记录
4. 锁定修改行,修改行锁定位(lb-lock byte)指向 ITL 事务槽
5. 数据修改可以进行
这就是一个事务的基本流程。
7. 块清除(Block Cleanouts)
我们继续看,当我们发出ᨀ交(commit)之后,Oracle 怎样来处理。
通过上一章的内容我们知道,Oracle 需要写出 Redo 来保证故障时数据可以被恢复;我们
也知道 Oracle 并不需要在ᨀ交时就写出变更的数据块。
那么在ᨀ交时,Oracle 需要对数据块进行哪些操作呢?
回忆一下上文,我们知道,在事务需要修改数据时,必须分配 ITL 事务槽,必须锁定行,
第 1 章 章名章名章名章名章名
·29·
必须分配回滚段事务槽和回滚空间记录前镜像。当事务ᨀ交时,Oracle 需要将回滚段上的事务
表信息标记为非活动,以便空间可以重用;那么还有 ITL 事务信息和锁定信息需要清除,以
记录ᨀ交。
由于 Oracle 在数据块上存储了 ITL 和锁定等事务信息,所以 Oracle 必须在事务ᨀ交之后
清除这些事务数据。这就是块清除。块清除主要要清除的数据有行级锁、ITL 信息(包括ᨀ
交标志,SCN 等)。
如果ᨀ交时修改过的数据块仍然在 Buffer Cache 之中,那么 Oracle 可以清除 ITL 信息,
这叫做快速块清除(Fast Block Cleanout),快速块清除还有一个限制,当修改的块数量超过
Buffer Cache 的约 10%,则对超出部分不再进行快速块清除。
如果ᨀ交事务的时候,修改过的数据块已经被写回到数据文件上(或大量修改超出 10%
的部分),再次读出该数据块进行修改,显然成本过于高昂,对于这种情况,Oracle 选择延迟
块清除(Delayed Block Cleanout),等到下一次访问该 Block 时再来清除 ITL 锁定信息,这就
是延迟块清除。Oracle 通过延迟块清除来ᨀ高数据库的性能,加快ᨀ交操作。
快速ᨀ交是最普遍的情况,我们来看一下延迟块清除的处理。继续前面的测试:
SQL> update emp set sal=4000 where empno=7788;
1 row updated.
SQL> update emp set sal=4000 where empno=7782;
1 row updated.
SQL> update emp set sal=4000 where empno=7698;
1 row updated.
更新完成之后,强制刷新 Buffer Cache,将 Buffer Cache 中的数据都写出到数据文件:
SQL> alter session set events = 'immediate trace name flush_cache';
Session altered.
此时再ᨀ交事务:
SQL> commit;
Commit complete.
由于此时更新过的数据已经写出到数据文件,Oracle 将执行延迟块清除,将此时的数据块
和回滚段转储出来:
SQL> alter system dump datafile 1 block 23642;
System altered.
SQL> alter system dump undo header '_SYSSMU6$';
System altered.
SQL> alter system dump datafile 2 block 85;
System altered.
研究一下,我们看数据块上的信息,ITL 事务信息仍然存在:
Start dump data blocks tsn: 0 file#: 1 minblk 23642 maxblk 23642
buffer tsn: 0 rdba: 0x00405c5a (1/23642)
scn: 0x0819.00406ddf seq: 0x01 flg: 0x04 tail: 0x6ddf0601
frmt: 0x02 chkval: 0x6b6a type: 0x06=trans data
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·30·
Block header dump: 0x00405c5a
Object id on Block? Y
seg/obj: 0x1f19 csc: 0x819.406ddf itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0009.02e.000036b0 0x00800081.0302.11 C--- 0 scn 0x0819.0036f205
0x02 0x0006.018.000036ce 0x00800055.02de.3f ---- 3 fsc 0x0002.00000000
数据块的锁定信息仍然存在:
tab 0, row 5, @0x1d19
tl: 40 fb: --H-FL-- lb: 0x2 cc: 8
col 0: [ 3] c2 4d 63
col 1: [ 5] 42 4c 41 4b 45
col 2: [ 7] 4d 41 4e 41 47 45 52
col 3: [ 3] c2 4f 28
col 4: [ 7] 77 b5 05 01 01 01 01
col 5: [ 2] c2 29
col 6: *NULL*
col 7: [ 2] c1 1f
tab 0, row 6, @0x1d41
tl: 40 fb: --H-FL-- lb: 0x2 cc: 8
col 0: [ 3] c2 4e 53
col 1: [ 5] 43 4c 41 52 4b
col 2: [ 7] 4d 41 4e 41 47 45 52
col 3: [ 3] c2 4f 28
col 4: [ 7] 77 b5 06 09 01 01 01
col 5: [ 2] c2 29
col 6: *NULL*
col 7: [ 2] c1 0b
tab 0, row 7, @0x1e54
tl: 40 fb: --H-FL-- lb: 0x2 cc: 8
col 0: [ 3] c2 4e 59
col 1: [ 5] 53 43 4f 54 54
col 2: [ 7] 41 4e 41 4c 59 53 54
col 3: [ 3] c2 4c 43
col 4: [ 7] 77 bb 04 13 01 01 01
col 5: [ 2] c2 29
col 6: *NULL*
col 7: [ 2] c1 15
第 1 章 章名章名章名章名章名
·31·
再来看回滚段的信息:
0x17 9 0x00 0x36ce 0xffff 0x0819.0040791b 0x00800055 0x0000.000.00000000
0x00000001 0x00000000
事务ᨀ交,事务表已经释放。
如果此时我们查询 SCOTT.EMP 表,数据库将产生延迟块清除:
SQL> set autotrace on
SQL> select * from scott.emp;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 4000 30
7782 CLARK MANAGER 7839 09-JUN-81 4000 10
7788 SCOTT ANALYST 7566 19-APR-87 4000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
。。。。。。。
14 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (FULL) OF 'EMP'
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
5 consistent gets
2 physical reads
60 redo size
注意到查询在此时产生了物理读和 REDO,这个 REDO 就是因为延迟块清除导致的。再
次查询,则不会继续生成 REDO 了:
SQL> /
EMPNO ENAME JOB MGR HIREDATE SAL COMM
DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·32·
。。。。。。
14 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (FULL) OF 'EMP'
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
再次转储一下该 Block 来看看此时数据块上的信息:
SQL> alter system dump datafile 1 block 23642;
System altered.
我们看到此时 ITL 事务信息已经清除,但是注意,这里的 Xid 和 Uba 信息仍然存在:
Start dump data blocks tsn: 0 file#: 1 minblk 23642 maxblk 23642
buffer tsn: 0 rdba: 0x00405c5a (1/23642)
scn: 0x0819.00407961 seq: 0x01 flg: 0x00 tail: 0x79610601
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
Block header dump: 0x00405c5a
Object id on Block? Y
seg/obj: 0x1f19 csc: 0x819.407961 itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0009.02e.000036b0 0x00800081.0302.11 C--- 0 scn 0x0819.0036f205
0x02 0x0006.018.000036ce 0x00800055.02de.3f C--- 0 scn 0x0819.0040791b
数据行的锁定位也已经清除:
tab 0, row 5, @0x1d19
tl: 40 fb: --H-FL-- lb: 0x0 cc: 8
col 0: [ 3] c2 4d 63
col 1: [ 5] 42 4c 41 4b 45
col 2: [ 7] 4d 41 4e 41 47 45 52
第 1 章 章名章名章名章名章名
·33·
col 3: [ 3] c2 4f 28
col 4: [ 7] 77 b5 05 01 01 01 01
col 5: [ 2] c2 29
col 6: *NULL*
col 7: [ 2] c1 1f
tab 0, row 6, @0x1d41
tl: 40 fb: --H-FL-- lb: 0x0 cc: 8
col 0: [ 3] c2 4e 53
col 1: [ 5] 43 4c 41 52 4b
col 2: [ 7] 4d 41 4e 41 47 45 52
col 3: [ 3] c2 4f 28
col 4: [ 7] 77 b5 06 09 01 01 01
col 5: [ 2] c2 29
col 6: *NULL*
col 7: [ 2] c1 0b
tab 0, row 7, @0x1e54
tl: 40 fb: --H-FL-- lb: 0x0 cc: 8
col 0: [ 3] c2 4e 59
col 1: [ 5] 53 43 4f 54 54
col 2: [ 7] 41 4e 41 4c 59 53 54
col 3: [ 3] c2 4c 43
col 4: [ 7] 77 bb 04 13 01 01 01
col 5: [ 2] c2 29
col 6: *NULL*
col 7: [ 2] c1 15
8. ᨀ交之后的 UNDO 信息
当ᨀ交事务之后,回滚段事务表标记标记该事务为非活动,继续再来看一下回滚段数据
块的信息:
我们看到这里 irb 指向了 0x40,此前的事务已经不可回滚。
Start dump data blocks tsn: 1 file#: 2 minblk 85 maxblk 85
buffer tsn: 1 rdba: 0x00800055 (2/85)
scn: 0x0819.00407baa seq: 0x01 flg: 0x04 tail: 0x7baa0201
frmt: 0x02 chkval: 0x053d type: 0x02=KTU UNDO BLOCK
********************************************************************************
UNDO BLK:
xid: 0x0006.025.000036ce seq: 0x2de cnt: 0x40 irb: 0x40 icl: 0x0 flg: 0x0000
偏移量列表也已经新增了一条信息 0x40 0x01b0 :
书名书名书名书名书名书名书名书名书名书名书名书名书名书名
·34·
Rec Offset Rec Offset Rec Offset Rec Offset Rec Offset
---------------------------------------------------------------------------
。。。。。。。。。。。。
0x33 0x097c 0x34 0x08d4 0x35 0x082c 0x36 0x0784 0x37 0x06dc
0x38 0x0634 0x39 0x058c 0x3a 0x04e4 0x3b 0x043c 0x3c 0x0394
0x3d 0x0310 0x3e 0x02b4 0x3f 0x0258 0x40 0x01b0
至于前镜像 0x3d 0x3e 0x3f 的信息,仍然存在:
*-----------------------------
* Rec #0x3d slt: 0x17 objn: 7961(0x00001f19) objd: 7961 tblspc: 0(0x00000000)
* Layer: 11 (Row) opc: 1 rci 0x00
Undo type: Regular undo Begin trans Last buffer split: No
Temp Object: No
Tablespace Undo: No
rdba: 0x00000000
*-----------------------------
uba: 0x00800055.02de.3c ctl max scn: 0x0819.003f594b prv tx scn: 0x0819.003f5d3e
KDO undo record:
KTB Redo
op: 0x04 ver: 0x01
op: L itl: xid: 0x0002.01d.000038ea uba: 0x008000c3.04b1.0c
flg: C--- lkc: 0 scn: 0x0819.0036f14f
KDO Op code: URP row dependencies Disabled
xtype: XA bdba: 0x00405c5a hdba: 0x00405c59
itli: 2 ispac: 0 maxfr: 4863
tabn: 0 slot: 7(0x7) flag: 0x2c lock: 0 ckix: 0
ncol: 8 nnew: 1 size: 0
col 5: [ 2] c2 1f
*-----------------------------
* Rec #0x3e slt: 0x17 objn: 7961(0x00001f19) objd: 7961 tblspc: 0(0x00000000)
* Layer: 11 (Row) opc: 1 rci 0x3d
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
第 1 章 章名章名章名章名章名
·35·
op: C uba: 0x00800055.02de.3d
KDO Op code: URP row dependencies Disabled
xtype: XA bdba: 0x00405c5a hdba: 0x00405c59
itli: 2 ispac: 0 maxfr: 4863
tabn: 0 slot: 6(0x6) flag: 0x2c lock: 0 ckix: 0
ncol: 8 nnew: 1 size: 1
col 5: [ 3] c2 19 33
*-----------------------------
我们大家可以猜想,虽然这个事务已经ᨀ交,不可以回滚了,但是在覆盖之前,这个前
镜像信息仍然存在,通过某种手段,我们应该仍然可以获得这个信息。
这个猜想显然是成立的

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论