DML语句与undo
当我们发出一条DML(比如update t set col1='B' where col1='A')语句时,其执行过程可大致概括为以下几步。
1、在shared pool里进行解析,从而生成执行计划。
假设根据执行计划,得出col1='A'的记录存放在10号数据文件的54号数据块里。
2、服务器进程在buffer cache里找一个可用的undo数据块,如果没有发现,则到undo表空间里找一个可用的undo块,并调入buffer cache。假设获得的undo数据块号为24号,位于11号undo数据文件里。
3、将改变前的值,也就是A放入11号undo数据块。
由于undo数据块发生了变化,于是产生重做记录,假设重做记录号为120。
行号 事务id file# block# row column value
120 T1 24 11 10 col1 A
4、在buffer cache里找到54号数据块。如果没有发现,则从10号数据文件里调入。
将改变后的值,也就是B放入54号数据块。
由于数据块发生了变化,于是产生重做记录,假设重做记录号为121。
行号 事务id file# block# row column value
121 T1 10 54 10 col1 B
5、控制权返回给用户,如果在SQL*Plus里执行DML,则表现为光标返回。
当用户发出commit命令时,触发LGWR进程,将120与121这两个重做记录写入联机日志文件,并将54号数据块和11号undo数据块头部所记录的事务状态标记设置为已提交。然后控制权返回给用户,如果在SQL*Plus里执行DML操作,则表现为光标返回。
6、这个时候,54号数据块以及11号undo块并不一定被DBWn写入数据文件。只有在脏数据块的数量达到一定程度才会被写入。
事务只要被提交或回滚,那么该事务所使用的undo块就可以被覆盖。对于上面的例子来说,当第 步,用户发出commit命令以后,11号undo块里的数据就可以被其他事务所覆盖。
undo的作用
在Oracle数据库中,undo主要有三大作用:提供一致性读(Consistent Read)、回滚事务(Rollback Transaction)以及实例恢复(Instance Recovery)。
一致性读是相对于脏读(Dirty Read)而言的。假设某个表T中有10000条记录,获取所有记录需要15分钟时间。当前时间为9点整,某用户A发出一条查询语句:select * from T,该语句在9点15分时执行完毕。当用户A执行该SQL语句到9点10分的时候,另外一个用户B发出了一条delete命令,将T表中的最后一条记录删 除并提交了。
那么到9点15分时,A用户将返回多少条记录?
如果返回9999条记录,则说明发生了脏读;如果仍然返回10000条记录,则说明发生了一致性读。很明显,在9点钟那个时间点发出查询语句时,表T中确 实有10000条记录,只不过由于I/O的相对较慢,所以才会花15分钟完成所有记录的检索。对于Oracle数据库来说,没有办法实现脏读,必须提供一 致性读,并且该一致性读是在没有阻塞用户的DML的前提下实现的。
那么undo数据是如何实现一致性读的呢?还是针对上面的例子。用户A在9点发出查询语句时,服务器进程会将9点那个时间点上的SCN号记录下来,假设该 SCN号为SCN9.00。那么9点整的时刻的SCN9.00一定大于等于记录在所有数据块头部的ITL槽中的SCN号(如果有多个ITL槽,则为其中最 大的那个SCN号)。
服务器进程在扫描表T的数据块时,会把扫描到的数据块头部的ITL槽中的SCN号与SCN9:00之间进行比较,哪个更大。如果数据块头部的SCN号比 SCN9.00要小,则说明该数据块在9点以后没有被更新,可以直接读取其中的数据;否则,如果数据块ITL槽的SCN号比SCN9.00要大,则说明该 数据块在9点以后被更新了,该块里的数据已经不是9点那个时间点的数据了,于是要借助undo块。
9点10分,B用户更新了表T的最后一条记录并提交(注意,在这里,提交或者不提交并不是关键,只要用户B更新了表T,用户A就会去读undo数据块)。 假设被更新记录属于N号数据块。那么这个时候N号数据块头部的ITL槽的SCN号就被改为SCN9.10。当服务器进程扫描到被更新的数据块(也就是N号 块)时,发现其ITL槽中的SCN9.10大于发出查询时的SCN9.00,说明该数据块在9点以后被更新了。于是服务器进程到N号块的头部,找到 SCN9.10所在的ITL槽。由于ITL槽中记录了对应的undo块的地址,于是根据该地址找到undo块,将undo块中的被修改前的数据取出,再结 合N号块里的数据行,从而构建出9点10分被更新之前的那个时间点的数据块内容,这样的数据块叫做CR块(Consistent Read)。对于delete来说,其undo信息就是insert,也就是说该构建出来的CR块中就插入了被删除的那条记录。随后,服务器进程扫描该 CR块,从而返回正确的10000条记录。
让我们继续把问题复杂化。假设在9点10分B用户删除了最后一条记录并提交以后,紧跟着9点11分,C用户在同一个数据块里(也就是N号块)插入了2条记 录。这个时候Oracle又是如何实现一致性读的呢(假设表T的initrans为1,也就是只有一个ITL槽)?因为我们已经知道,事务需要使用ITL 槽,只要该事务提交或回滚,该ITL槽就能够被重用。换句话说,该ITL槽里记录的已经是SCN9.11,而不是SCN9.10了。这时,ITL槽被覆盖 了,Oracle的服务器进程又怎能找回最初的数据呢?
其中的秘密就在于,Oracle在记录undo数据的时候,不仅记录了改变前的数据,还记录了改变前的数据所在的数据块头部的ITL信息。因此,9点10 分B用户删除记录时(位于N号块里,并假设该N号块的ITL信息为[Undo_block0 / SCN8.50]),则Oracle会将改变前的数据(也就是insert)放到undo块(假设该undo块地址为Undo_block1)里,同时在 该undo块里记录删除前ITL槽的信息(也就是[Undo_block0 / SCN8.50])。删除记录以后,该N号块的ITL信息变为 [Undo_block1 / SCN9.10];到了9点11分,C用户又在N号块里插入了两条记录,则Oracle将插入前的数据(也就是delete两条记录)放到undo块(假 设该undo块的地址为Undo_block2)里,并将9点11分时的ITL槽的信息(也就是[Undo_block1 / SCN9.10])也记录到该undo块里。插入两条记录以后,该N号块的ITL槽的信息改为 [Undo_block2 / SCN9.11]。
那么当执行查询的服务器进程扫描到N号块时,发现SCN9.11大于SCN9.00,于是到ITL槽中指定的Undo_block2处找到该undo块。 发现该undo块里记录的ITL信息为[Undo_block1 / SCN9.10],其中的SCN9.10仍然大于SCN9.00,于是服务器进程继续根据ITL中记录的Undo_block1,找到该undo块。发现 该undo块里记录的ITL信息为[Undo_block0 / SCN8.50],这时ITL里的SCN8.50小于发出查询时的SCN9.00,说明这时undo块包含合适的undo信息,于是服务器进程不再找下 去,而是将N号块、Undo_block2以及Undo_block1的数据结合起来,构建CR块。将当前N号的数据复制到CR块里,然后在CR块里先回 退9点11分的事务,也就是在CR块里删除两条记录,然后再回退9点10分的事务,也就是在CR块里插入被删除的记录,从而构建出9点钟时的数据。 Oracle就是这样,以层层嵌套的方式,查找整个undo块的链表,直到发现ITL槽里的SCN号小于等于发出查询时的那个SCN号为止。正常来说,当 前undo块里记录的SCN号要比上一个undo块里记录的SCN号要小。
但是在查找的过程中,可能会发现当前undo块里记录的ITL槽的SCN号比上一个undo块里记录的SCN号还要大。这种情况说明由于事务被提交或回 滚,导致当前找到的undo块里的数据已经被其他事务覆盖了,于是我们无法再找出小于等于发出查询时的那个时间点的SCN号,这时Oracle就会抛出一 个非常经典的错误——ORA-01555,也就是snapshot too old的错误。
回滚事务则是在执行DML以后,发出rollback命令撤销DML所作的变化。Oracle利用记录在ITL槽里记录的undo块的地址找到该undo块,然后从中取出变化前的值,并放入数据块中,从而对事务所作的变化进行回滚。
实例恢复则是在SMON进程完成前滚并打开数据库以后发生。SMON进程会去查看undo segment头部(所谓头部就是undo segment里的第一个数据块)记录的事务表(每个事务在使用undo块时,首先要在该undo块所在的undo segment的头部记录一个条目,该条目里记录了该事务相关的信息,其中包括是否提交等),将其中既没有提交也没有回滚,而是在实例崩溃时被异常终止的 事务全部回滚。
关于UNDO_RETENTION
UNDO_RETENTION 通 常默认是900 秒,也就是15 分钟。值得注意是,undo_retention 只是指定undo 数据的过期时间,并不是说,undo 中的数据一定会在undo表空间中保存15 分钟,比如说刚一个新事务开始的时候,如果undo 表空间已经被写满,则新事务的数据会自动覆盖已提交事务的数据,而不管这些数据是否已过期。因此,当你创建一个自动管理的undo 表空间时,还要注意其空间大小,要尽可能保证undo 表空间有足够的存储空间。
同时还要注意,也并不是说,undo_retention 中指定的时间一过,已经提交事务中的数据就立刻无法访问,它只是失效,只要不被别的事务覆盖,它会仍然存在,并可随时被flashback 特性引用。如果你的undo表空间足够大,而数据库又不是那么繁忙,那么其undo_retention 参数的值并不会影响到你,哪怕你设置成1,只要没有事务去覆盖undo 数据,它就会持续有效。因此呢,这里还是那句话,要注意undo 表空间的大小,保证其有足够的存储空间。因此如果闪回表时所需要的UNDO数据,由于保留的时间超过了UNDO_RETENTION的所指定的值,从而导 致该UNDO数据被其他事务覆盖的话,那么就不能闪回到指定时间了。 表空间上指定了retention guarantee选项使UNDO数据在一定时间内不被覆盖。
修改UNDO_RETETION的值命令如下:
SQL> alter system set undo_retention=600 scope=both;
启用undo guarantee
SQL> alter tablespace undotbs1 retention guarantee;
禁用undo guarantee
SQL> alter tablespace undotbs1 retention noguarantee;
在AUM模式下,当活动的事务使用undo segment时,事务可以在不同的undo segment之间动态交换undo空间,也就是在不同的undo segment里交换extents。当一个正在执行的事务需要更多的undo空间时,首先会重用当前undo segment里的可用空间;如果当前undo segment里的可用空间(也就是extents)不足时,则会按照下面的步聚获得所需要的extent:
获取undo表空间里可用的、空的extents;
获取其他undo segment里的expired状态的extents;
如果undo表空间里的数据文件启用了自动扩展(autoextend on),则数据文件进行自动扩展;
如果undo表空间里的数据文件没有启用自动扩展,则获取其他undo segment里的INACTIVE状态的extents;
如果以上步骤均无法获得可用空间时,报空间不足的错误消息。
Tuning of Undo Retention
Oracle Database automatically tunes the undo retention period based on undo tablespace size and system activity. You can optionally specify a minimum undo retention period (in seconds) by setting the UNDO_RETENTION initialization parameter. The exact impact this parameter on undo retention is as follows:
The UNDO_RETENTION parameter is ignored for a fixed size undo tablespace. The database always tunes the undo retention period for the best possible retention, based on system activity and undo tablespace size.
For an undo tablespace with the AUTOEXTEND option enabled, the database attempts to honor the minimum retention period specified by UNDO_RETENTION. When space is low, instead of overwriting unexpired undo information, the tablespace auto-extends. If the MAXSIZE clause is specified for an auto-extending undo tablespace, when the maximum size is reached, the database may begin to overwrite unexpired undo information. The UNDOTBS1 tablespace that is automatically created by DBCA is auto-extending.
Oracle Database automatically tunes the undo retention period based on how the undo tablespace is configured.
- If the undo tablespace is configured with the AUTOEXTEND option, the database dynamically tunes the undo retention period to be somewhat longer than the longest-running active query on the system. However, this retention period may be insufficient to accommodate Oracle Flashback operations. Oracle Flashback operations resulting in snapshot too old errors are the indicator that you must intervene to ensure that sufficient undo data is retained to support these operations. To better accommodate Oracle Flashback features, you can either set the UNDO_RETENTION parameter to a value equal to the longest expected Oracle Flashback operation, or you can change the undo tablespace to fixed size.
- If the undo tablespace is fixed size, the database dynamically tunes the undo retention period for the best possible retention for that tablespace size and the current system load. This best possible retention time is typically significantly greater than the duration of the longest-running active query.If you decide to change the undo tablespace to fixed-size, you must choosea tablespace size that is sufficiently large. If you choose an undo tablespacesize that is too small, the following two errors could occur:
DML could fail because there is not enough space to accommodate undo for new transactions.
Long-running queries could fail with a snapshot too old error, which means that there was insufficient undo data for read consistency.
Retention Tuning and Alert Thresholds
For a fixed-size undo tablespace, the database calculates the best possible retention based on database statistics and on the size of the undo tablespace. For optimal undo management, rather than tuning based on 100% of the tablespace size, the database tunes the undo retention period based on 85% of the tablespace size, or on the warning alert threshold percentage for space used, whichever is lower. (The warning alert threshold defaults to 85%, but can be changed.) Therefore, if you set the warning alert threshold of the undo tablespace below 85%, this may reduce the tuned size of the undo retention period. For more information on tablespace alert thresholds.
管理undo
数据库中可以同时存在多个undo表空间,但是在一个时间点上,数据库只能使用一个undo表空间。如果我们将undo_tablespace参数设置为另外一个undo表空间的名字,则这叫做undo表空间的切换。
当我们切换undo_tablespace时,如果旧的undo表空间上有事务正在执行,则该旧的undo表空间变成pending offline状态。同时用户的事务可以正常执行,切换操作立刻结束,它并不会等待使用旧的undo表空间的事务结束。发生切换以后,所有新的事务所产生 的undo数据不会存放在该旧的undo表空间里,而是会使用新的undo表空间。我们不能使用pending offline状态的undo表空间,该状态下的undo表空间也不能被删除。最终,当旧的undo表空间上的所有的事务都提交以后,旧的undo表空间 从pending offline状态变成offline状态,这时我们才可以删除该旧的undo表空间。
在删除undo表空间时,与删除一般的表空间不同。也就是说,如果drop tablespace后面跟的是某个undo表空间的名字,就相当于发出drop tablespace including contents命令。一个undo 表空间只有在当前没有被活动的事务使用的时候才能被删除。如果undo表空间还包含某个未结束的事务,比如某个事务异常中断,但是还没有回滚,这时 drop tablespace会报错。但是,drop tablespace命令能够删除那些含有inactive状态的undo块(这些undo块还没有expired)的undo表空间。这样就有可能发生 ORA-1555错。因此,我们在切换undo_tablespace以后,应该在等待的时间超过undo_retention的长度以后再删除旧的 undo表空间。
在判断undo表空间应该设置多大时,借助Database Control所提供的Undo Advisory会更加简单。这里,横轴为undo retention,以秒为单位,纵轴为undo表空间大小尺寸,以MB为单位。
参考至:《教你成为10g OCP》韩思捷著
本文原创,转载请注明出处、作者
http://docs.oracle.com/cd/E11882_01/server.112/e25494/undo.htm#ADMIN10180
如有错误,欢迎指正
邮箱:czmcj@163.com