教你如何成为Oracle 10g OCP - 第七章 undo表空间管理


概念: 对于DML来说,只要修改了数据块,Oracle就会将修改前的数据保留下来。
分类: 从Oracle9i开始,undo管理分为两类,Auto Undo Manangement(AUM)和MUM .
Oracle9i之前只能使用MUM, MUM中undo segment又叫做rollback segment, 从9i开始
Oracle就建议用AUM .

 

7.1  DML语句与UNDO

例子:  update t set col1='A' where col1='B' ;
(1). 在shared pool 中进行解析生成执行计划
(2). 假设col1='B'的记录存放在10# file, 54号block中;
(3). Server process在buffer cache中找一个可用的undo block,若没有,到undo
表空间中找一个可用的undo block,调入buffer cache中,假设获得的undo块号为24,
是11号undo datafile中;
(4). 将改变前的col1的值B存放在24号undo block中;
(5). 这时buffer cache中的undo block发生变化,于是产生重做记录(在log buffer中)
重做记录行号    事务ID    file#    blk#   row   column   value
   120            T1        11      24     10     col1     B
(6). 在buffer cache中查找54号数据块,如果没有,从10号数据文件中调入buffer
cache ;
(7). 将update后的值'A'放入54号block (内存中叫做buffer)中;
(8). 由于54号数据块在buffer cache中发生变化,于是在log buffer中产生一笔重做
记录 。
重做记录行号    事务ID    file#    blk#   row   column   value
   121            T1        10      54    10     col1      A 
(8). 控制权返回给用户,表现为光标返回。
(9). 当用户发出commit, 触发LGWR, 将120,121重做记录写入redo log file中,并将
24号和54号数据块头部记录的事务状态设置为已提交;
(10). 这时在buffer cache中更新后的54号data block和24号undo block并不一定被
DBWn写入datafile中,只有当dirty block达到一定程度或发生完全检查点才会被写入,
因为增量检查点触发DBWn启动的条件的优先级较低。  事务只要被提交或回滚,该事务
所用的undo块就可以被覆盖。

 


7.2 Undo的作用:  一致性读和回滚事务

(1). 一致性读(Consistent Read) -- 是相对与脏读(dirty read)而言 .

ITL槽  --- 每当一个事务要更新一个block中的数据时,必须先在block的头部获取一个
可用的ITL槽,将当前的事务ID,事务所使用的undo block的地址,SCN号及当前事务是否
提交的标记等信息注册到ITL槽中。
 
脏读 ---  8点开始select一个表中10000笔记录,获得结果需要5分钟,在8:01的时候另
一个用户delete删除其中的一笔记录,并提交,如果查询返回9999,那么发生脏读,如果
是10000笔,则发生一致读,对于Oracle来说,必须提供一致读。 特别提醒,如果是以上
两个动作是同一个session上发生的,那么查出来的记录会是9999笔 。


Undo是如何实现一致读 ---- 

用户A在8点发出查询select, Server process会将8点那个时间点上的SCN
记录下来,假设为SCN8.00, 那么8点整的SCN8.00一定大于等于记录在所有
数据block头部ITL槽中的SCN号(如果有多个ITL槽,取最大那个SCN),Server
process扫描表中的block时,会将扫描到的block头部的ITL槽中的SCN号与
SCN8.00对比,如果block头部的SCN小于SCN8.00, 说明block在8点后没有
被更新,若大于,说明该块在8点后有更新,已不是8点时的数据了,要借助
undo block, 8:01分,B用户更新了表T的最后一笔记录并提交(注意,这里
不管是否提交,只要用户B更新了表T,用户A就会去读undo数据块),假设最后
这条被更新的记录属于N号block, 那么这时N号block头部的ITL槽的SCN号被
改为SCN8.01 ,当Server process查询扫描到这个被更新的N号数据块时,发现
该块ITL槽中SCN8.01大于用户A发出查询时候的SCN8.00, 说明该块被更新了,
于是server process到被更新的N号数据块头部找到SCN8.01所在的ITL槽,
这个ITL槽中记录了对应的UNDO块的地址,于是根据地址找到undo块,将undo
块中存放的的前镜像取出,再结合N号块的数据行,从而构造出8:01分被更新
之前的那个时间点的数据块内容。 这样的block叫做CR块(Consistent Read),
比如delete, 其undo信息是insert, 即构建CR块中就插入了被删除的那条记录。
随后,server process扫描该CR块,从而得到正确的10000笔记录。


----------------------- 


假设在同一个block(N号块),在9点用户A发出查询,需要15分钟查询完成,9点
10分的时候B用户删除了最后一条记录并提交,9点11分,C用户在同一个block
中插入了两条记录,假设表的initrans 及ITL槽只有1个),Oracle如何实现一致
读呢 ?  提交回滚导致ITL槽记录的已经是SCN911, 而不是SCN910, 即ITL槽
被覆盖了,Oracle如何找回最初的数据呢 ?

Oracle在记录undo数据的时候,不仅记录了改变前的数据,还记录了改变前
的数据所在的数据块头部的ITL槽信息(ITL信息包括事务ID,使用的undo block
的地址,SCN号及当前事务是否提交等标记信息) 。

-- 假设N号数据块中,8:50时的ITL信息时undo_block0/SCN850
-- 9:10分,B删除记录时,Oracle将改变前的数据放到undo块,假设该undo块
地址为undo_block1, 同时在该undo块中记录删除前ITL槽信息,即undo_block0/
SCN850) .
-- 删除记录后,N号数据块的ITL信息变成undo_block1/SCN910 .
-- 9:11分,用户在N号块中插入两记录,ORACLE将插入前数据(即删除两条记录)
放在undo块(undo_block2)中,并将9:11分的ITL槽信息(即undo_block1/SCN910)
也记录到undo块(undo_block2)中。
-- 插入两条记录后,该N号块的ITL槽信息变成 undo_block2/SCN911 .

OK, 我们开始分析查询过程。

-- 我们在9点查询数据到N号数据块时,查询到的SCN900小于最新的SCN911,那么
说明数据块被更新,于是到数据块头部ITL槽中指定的undo_block2地址找该undo块,
发现该undo块头部记录的ITL为undo_block1/SCN910, SCN900仍然小于SCN910,那么
Oracle需要继续根据undo_block1地址找undo块,发现该undo记录的ITL信息为
undo_block0/SCN850, 这时SCN850undo_block2 和 undo_block1数据结合起来,构建CR块,将当前N号块数据复制到
CR块中,先在CR块中删除两记录(退到9:11分),然后插入被删除的最后一笔(退回
到9:10分),从而构建出9点时的数据。


--- 经典的ORA-01555错误。 在查的过程中,可能会发现当前undo块中记录的ITL
槽的SCN号比上一个undo块里记录的SCN还要大,这种情况说明由于事务被提交或
回滚,导致当前的undo块里的data被其他事务覆盖了,无法找到小于等于当前查询
时间点的SCN号了。 报snapshot too old的错误。


--- 回滚事务
Oracle利用记录在每个block中的ITL槽里记录的undo 块的地址找到该undo块,然后
从中取出变化前的值,读入到buffer cache中,从而对变化进行回滚。


--- 实例恢复
SMON 进程完成前滚,然后Open数据库,SMON检查undo segment的第一个block记录
的事务表,将其中未提交也未回滚的异常终止的事务全回滚。

SMON进行实例恢复时,先从controlfile中获得检查点位置,SMON到redo log中
找到该检查点位置,然后从检查点位置开始向下,应用所有的redo record, 在
buffer cache中恢复到实例crash那个时间点的状态。(实例恢复是因为:  redo
record已写入redo log ,但检查点队列中的dirty data还没有完全写入datafile) .

 


7.3 配置AUM

AUM= Auto Undo Manangement 

要配置AUM ,首选我们需要配置初始化参数undo_management,该参数用来说明undo
的管理方式,取值分别是 auto, manual .

auto   - 表示采用AUM来管理undo .
manual - 表示采用MUM来管理undo, 表示我们要手工创建rollback segment等。

在AUM管理模式下,我们只需要创建undo表空间,并指定初始化参数undo_tablespace,
剩下的工作,包括undo segment的创建,扩展,收缩,删除等,都由数据库自动完成。

如果我们指定了undo_management为auto, 没有指定undo_tablespace,那么系统会查找
第一个可用的undo表空间,如果没找到,则使用位于system系统表空间里的rollback
segment, 这样会导致系统表空间的压力。

 

-- 当发生DML操作的时候,服务器进程会选择一个undo segment,AUM采用的是事务绑定
undo segment的算法。
A. 首先尝试将每个undo segment绑定一个事务,也就是每个undo segment上只被一个
事务所使用。 
B. 如果不能发现完全空闲的(空闲表示没有与任何事务绑定)undo segment, 则系统会
尝试将其他脱机的undo segment联机。
C. 如果没有可用的undo segment进行联机,则会尝试创建一个新的undo segment。
D. 如果上面的步骤都没有成功,比如由于没有可用空间了,而不能创建新的undo segment,
则事务绑定算法会去尝试找最早被使用的那个undo segment。这种情况下,不同的多个
事务才会在一个相同的undo segment里同时运行。

 

---------------------------------------------------------------------
undo segment的创建、online以及extent的分配原则。

When the undo tablespace is created, a number of undo segments are created.
These are named using the _SYSSMU$ algorithm. Because of the new name,
some commands (alter system dump undo header) require that the undo segment
name be enclosed in double quotes.

The number of undo segments created and brought online is a function of the
SESSIONS parameter. The algorithm is roughly 1 undo segment for each 5 sessions.
All undo segments are placed online when the sessions parameter is set to 46
or greater. The lower limit was not tested, as the minimum value for the
sessions parameter in the test database was 16, which was derived from the
minimum number of processes (10) for the database. These are sized according
to the autoallocate algorithm for locally managed tablespaces. The basic algorithm
is that the first 16 extents are 64k in size. The subsequent allocation method
is the next 63 extents of 1m, the next 120 extents of 8m and all additional
extents at 64m.

The first block of the first undo extent is reserved as the header and not
indicated in the data dictionary views. As with Oracle 8, the second block
of the undo segment is not used initially and may or may not be allocated
at a later time. This is one of those anomalies that has no consistent pattern
and is therefore hard to explain. 

当undo表空间被创建时,很多的undo segments被创建,这些segments以_SYSSMU$
规则来命名。 因为新的名称,一些命令比如alert system dump undo header 需要在
undo segment的名称上加注双引号。

Undo segment创建和在线数量是由参数SESSIONS决定的,粗略算法为每5个session对应
一个undo segment, 当sessions参数设置为46或更大时所有的undo segments都处于
online状态。 下限没有被测试过,因为在测试数据库中sessions参数最小值是16,
这个值来自于数据库中参数processes的最小值10,这些大小是参考本地管理表空间
(LMT)自动分配算法而来。 基本算法是: 前16个extents大小为64K, 随后63个extents
是每个1M, 然后120个extents是每个8M, 再后面的extents大小都是64M不变。

第一个undo extent的第一个block保留作为头部,没有在数据字典中标示出来,与Oracle
8 一样,undo segment的第二个block最初没有被使用,后面一段时间也许被分配,也许
不会,这是异常现象之一,没有统一的模式,因此很难解释。

---------------------------------------------------------------------

 

既然存在undo segment的扩张(当并发事务的数量超过在线undo segment的数量时undo
segment被smon自动创建),就存在undo segment的收缩。收缩由SMON完成,在下面的
情况下会收缩undo segments:
A. 每隔12个小时会收缩一次,删除那些idle状态的extents;
B. 当前台进程进行DML而需要undo时,发现空间不够用,则会唤醒SMON进行一次收缩。
也就是说,将其他undo segment里暂时没被使用的extent拿过来用。

Oracle为undo的管理提供了另一个参数:undo_retention。该参数以秒为单位,表示当
事务提交或回滚以后,该事务所使用的undo块里的数据需要保留多长时间;当保留的时
间超过undo_retention所指定的时间以后,该undo块才能够被其他事务覆盖。

 

当我们使用AUM,并设置了undo_retention以后,undo块就存在四种状态。
a. Active:表示正在使用该undo的事务还没有提交或回滚。
b. Inactive:表示该undo上没有活动的事务,该状态的undo可以被其他事务覆盖。
c. Expired:表示该undo持续inactive的时间超过undo_retention所指定的时间。
d. Freed:表示该undo块内容是空的,从来没有被使用过。

 

当活动的事务使用undo segment时,在AUM下,事务可以在不同的undo segment
之间动态交换undo空间(也就是extents),当一个执行的事务需要更多的undo空间
时,首先会重用当前undo segment里的可用空间,如果不足,则按照下面步骤获
取所需要的extent :
a. 获取undo表空间中可用的,空的extents;
b. 获取其他undo segment里的expired状态的extents;
c. 如果undo数据文件启用了自动扩展,则文件自动扩展;
d. 如果undo文件都没有启用自动扩展,则获取其他undo segment中inactive的;
e. 如果以上步骤均无法获得可用空间时,报空间不足的错误消息.

从这里可以看出,在AUM里,分配undo块的算法不仅有效,而且会尽可能地将INACTIVE
状态的extents保留足够长的时间。在Oracle10g里,如果undo表空间有足够的可用空间
,则Oracle会将undo信息保留的时间与当前运行时间最长的那个查询所需要的时间相同。

 

undo_retention 默认值为900秒,Oracle不会自动调整undo_retention,使用
我们设置的该值作为undo保留的时间。

如果我们没有为该参数指定值,或指定了0,则Oracle会自动调整undo retention,
并以900秒作为最低值。Oracle 10g 会每隔30秒钟就收集统计信息来自动调整undo
retention,收集的信息包括运行时间最长的查询、产生undo的速度等。

如果设置undo_retention为0,则实例会获取运行时间最长的那个查询所需要的时间,
比如为N秒,然后将undo信息保留N秒。当undo表空间的尺寸太小,而不能保留这个最长
的时间时,则会尽可能地利用现有的可用空间来让undo保留的时间尽可能长,并不会立
刻扩展undo数据文件。除非要覆盖的undo信息是在距今900秒以内发生的,才会去扩展数
据文件。

 


7.4  管理undo表空间

与普通表空间管理差不多。

数据库中可以同时存在多个undo表空间,但是在一个时间点上,数据库只能使用
一个undo表空间。如果我们将undo_tablespace参数设置为另外一个undo表空间的
名字,则这叫做undo表空间的切换。

 

如果有事务还在undotbs1上执行,这时我们切换undo_tablespace时,旧的undo
表空间变成pending offline状态,不能被使用,也不能被删除。最终,当旧的
undo表空间上的所有的事务都提交以后,旧的undo表空间从pending offline状
态变成offline状态,这时我们才可以删除该旧的undo表空间。

-- 实验:

a. 可以先写个程序段,指定使用undotbs1 ;
b. 程序中的update动作不提交 ;
c. 再切换undo表空间到undotbs2 ;
d. 查看旧的表空间转台;
e. 提交update动作后再次查看状态;


一个undo 表空间只有在当前没有被活动的事务使用的时候才被删除。如果drop
tablespace后面跟的是某个undo表空间的名字,就相当于发出drop tablespace
... including ontents命令。

我们在切换undo_tablespace以后,应该在等待的时间超过undo_retention的长
度以后再删除旧的undo表空间。因为drop tablespace命令能够删除那些含有
inactive状态的undo块(这些undo块还没有expired)的undo表空间, 这可能
导致ora-01555错误。

 


retention guarantee

-- 为了彻底防止保留时间小于undo_retention的undo 数据块被覆盖,Oracle引入
了retention guarantee属性。

retention guarantee属性可以在创建undo表空间时指定,也可以在创建完毕
以后设置:

SQL> create undo tablespace undonew1 datafile
'/u01/app/oracle/oradata/ora10g/undonew01.dbf'
size 10M autoextend on maxsize 100M retention guarantee;

SQL> alter tablespace undonew retention guarantee;

若要取消retention guarantee属性,则使用下列命令:
SQL> alter tablespace undonew retention noguarantee;

 

 

--  计算undo空间的大小

在我们计算undo表空间大小时,可以借助视图v$undostat。Oracle每隔10分钟
更新该视图,将这10分钟里产生的undo块的个数记录在该视图里。Oracle在
v$undostat里保留最近7天的数据,也就是1008行记录。

因此,通过该视图,我们可以计算undo表空间应该设置多大,Oracle提供了如下公式:
UndoSpace = Undo Retention * Undo Per Second + overhead(24 undo blocks)

其中,undo retentio就是系统中运行时间最长的那个查询所花费的时间,我们把该
时间设置到初始化参数undo_retention上去。而Undo Per Second则可以通过查询
v$undostat来计算:

Select blks/((end-begin)*3600) as "Undo Per Second" from
(Select min(begin_time) begin,max(end_time) end,sum(undoblks)
blks from v$undostat);

当然,在判断undo表空间应该设置多大时,借助Database Control所提供的Undo
Advisory会更加简单。

请使用浏览器的分享功能分享到微信等