(1)Undo的作用 Undo主要有以下几个作用:
1)事务回滚(Rollback Transaction)。当一个事务修改表中数据的时候,该数据修改前的值(即前镜像,Before Image)会被存放在Undo段中,当用户回滚事务(ROLLBACK)时,Oracle将会利用在数据块ITL槽中记录的Undo块地址(Undo Block Address,Uba),然后找到相应的Undo块,接着利用其中的Undo数据(即前镜像)来将修改的数据恢复到原来的值,从而实现对事务所做的改变进行回滚。
2)事务恢复(Transaction Recovery)。实例恢复(Instance Recovery)的第一阶段称为前滚(Rolling Forward)或者缓存恢复(Cache Recovery),第二阶段称为回滚(Rolling Back)或者事务恢复。前滚和回滚是Oracle数据库实例发生意外崩溃,重新启动的时候,由SMON进行的自动恢复的过程。所谓的前滚是应用Redo来恢复Buffer Cache的数据,将Buffer Cache恢复到Crash之前状态,所以此时Buffer Cache中既有崩溃时已经提交但还没有写入数据文件的脏块,还有事务被突然终止而导致的既没有提交又没有回滚的事务的脏块(也就是没有COMMIT,但是DBWn已经将改变的数据刷新到底层磁盘)。前滚完成之后就可以确保联机Redo日志中所有已提交的事务操作的数据写回到数据文件中。接下来,前滚之后,任何未提交的更改必须被撤销,而回滚是在数据库做完前滚操作后并打开数据库的情况下完成的,SMON会利用Llndo信息将未提交的事务全部进行回滚。具体来说,SMON进程在完成前滚后,查看Undo段头(Undo段的第1个数据块)记录的事务表(每个事务在使用Undo块时,首先要在该Undo块所在的Undo段头记录一个条目,该条目里记录了该事务相关的信息,其中包括是否提交等),将其中既没有提交也没有回滚,而是在实例崩溃时被异常终止的事务全部回滚。
3)提供一致性读(Consistent Read)。Oracle是一个多用户系统,当一个会话开始读取数据还未结束读取之前,可能会有其他会话修改了该会话将要读取的数据。如果会话读取到修改后的数据,那么就会造成数据的不一致,出现了脏读(Dirty Read)。所以,一致性读是相对于脏读而言的。在Oracle中,一致性读是通过Undo来实现的,一致性读就是为了保证数据的一致性。在一般情况下,普通查询都是一致性读。
举例来说,假设某个表T中有1万条记录,获取所有记录需要15min时间。当前时间为9点整,某用户A发出一条查询语句:“SELECT*FROM T;”,该语句在9点15分时执行完毕。当用户A执行该SQL语句到9点10分的时候,另外一个用户B发出了一条DELETE命令,将T表中的最后一条记录删除并提交了。那么到9点15分时,A用户将返回多少条记录?如果返回9999条记录,那么说明发生了脏读;如果仍然返回1万条记录,那么说明发生了一致性读。很明显,在9点钟那个时间点发出查询语句时,表T中确实有1万条记录,只不过由于I/O的相对较慢,所以才会花15min完成所有记录的检索。对于Oracle数据库来说,必须提供一致性读,并且该一致性读是在没有阻塞用户的DML操作的前提下实现的。
那么Undo数据是如何实现一致性读的呢?在Oracle数据库中的Buffer Cache中的数据块上都会有最后一次修改数据块时的SCN。如果一个事务需要修改数据块中数据,那么会先在回滚段中保存一份修改前数据和SCN的数据块,然后再更新Buffer Cache中的数据块的数据及其SCN,并标识其为“脏”数据。当其他进程读取数据块时,会先比较数据块上的SCN和自己发出SQL语句时刻的SCN,分为以下两种情况:
①如果该数据块头部的ITL槽上记录的SCN大于自己查询时刻的SCN,那么表示该块被更新过,此时就要借助Undo块了。在该数据块头部的ITL槽上记录了对应的Undo块的地址(Uba),根据Uba就可以找到对应的Undo块。如果发现该Undo块的ITL槽的SCN号也较大,证明该Undo块也不可用,那么需要在该块的ITL糟上继续寻找上一个Undo块地址,层层递归,最终找到SCN号比发出查询的SCN号小的Undo块,将该Undo块中的被修改前的数据取出,从而构建出发出SQL语句时刻的数据块内容,这样的数据块称为CR(Consistent Read)块。但是在查找的过程中,可能会发现当前Undo块里记录的ITL槽的SCN号比上一个Undo块里记录的SCN号还要大。这种情况说明由于事务被提交或回滚,导致当前找到的Undo块里的数据已经被其他事务覆盖了,于是就无法再找出小于等于发出查询时的那个时间点的SCN号,这时Oracle就会抛出一个非常经典的错误—ORA-1555,也就是snapshot too old(快照过旧)的错误。对于DELETE来说,其Undo信息就是INSERT,也就是说该构建出来的CR块中就插入了被删除的那条记录。
②如果数据块头部的ITL槽(事务槽)上记录的SCN小于等于自己查询时刻的SCN,那么分为两种情况:第一,若被查询的块上没有活动的事务,则表示该块没有被更新过,是可用的,可以直接读取该数据块上的数据;第二,若被查询的块上有活动的事务,则需要找Undo的前镜像数据。
4)实现闪回功能。闪回功能中的闪回查询(Flashback Query)、闪回版本查询(Flashback Version Query)、闪回事务查询(Flashback Transaction Query)和闪回表(Flashback TABLE)都是基于Undo表空间中的回滚信息实现的。
(2)Undo段存储的内容 Redo中只会记录少量信息,这些信息足以重演事务;同样Undo中也只记录精简信息,这些信息足以撤销事务。具体来说:
1)对于INSERT操作,回滚段只需要记录插入记录的ROWID,如果回退,那么只需将该记录根据ROWID删除即可。
2)对于UPDATE操作,回滚段只需要记录被更新字段的旧值即可(前镜像),回退时通过旧值覆盖新值即可完成回滚。
3)对于DELETE操作,Oracle则必须记录整行的数据,在回滚时,Oracle通过一个反向操作恢复删除的数据。
总结一下:对于相同数据量的数据操作,通常INSERT产生最少的Undo,UPDATE产生的Undo居中,而DELETE操作产生的Undo最多。所以,当一个大的DELETE操作失败或者回滚,总是需要很长的时间,并且会有大量的Redo生成。所以通常在进行大规模数据删除操作时,推荐通过分批删除分次提交,以减少对于回滚段的占用和冲击。
(3)块清除 块清除(Block Cleanout)是指清除存储在数据块头部与锁相关的信息,其实质是在清除块上的事务信息,包括数据的行级锁和ITL信息(包括提交标志、SCN等),块清除不需要生成Redo日志。Oracle的块清除有两种:快速块清除(Fast Commit Cleanout)和延时块清除(Delayed Block Cleanout)。
通过命令“alter system dump undo header '回滚段名称';”可以将Undo段头信息dump出来,可以很明显地看到事务表(TRN TBL)信息,其中,状态(state)为10代表活动事务,状态(state)为9表示INACTIVE。Dba列表示该事务对应的Undo Block Dba地址。每个事务处理只分配给一个Undo段,一个Undo段可以同时服务多个事务处理。
在提交事务的时候,如果被修改过的数据块仍然在Buffer Cache之中,那么Oracle可以清除ITL信息,这称为快速块清除(Fast Block Cleanout),也称为提交清除(Fast Commit Cleanout)。快速块清除还有一个限制,当修改的块数量超过Buffer Cache约10%,则对超出部分不再进行快速块清除。
在提交事务的时候,如果被修改过的数据块已经被写回到数据文件上(或大量修改超出Buffer Cache的10%的部分),再次读出该数据块进行修改,显然成本过于高昂,对于这种情况,Oracle选择延迟块清除(Delayed Block Cleanout),即在提交的时候只会清理Undo Segment Header中的事务表信息,而Data Block上的事务标志不会清除,等到下一次访问该Block时再来清除ITL锁定信息,这就是延迟块清除。Oracle通过延迟块清除来提高数据库性能,加快提交操作。如果Oracle不对块完成这种延迟清除,那么COMMIT的处理可能会很长,COMMIT必须重新访问每一个块,可能还要从磁盘将块再次读入内存。在一个OLTP系统中,可能很少看到这种情况发生,因为OLTP系统的特点是事务都很短小,只会影响为数不多的一些块。
如果执行一个大的INSERT、UPDATE或DELETE,会影响数据库中的许多块,那么就有可能在此之后,第一个“接触”块的查询会做延迟块清除,从而生成Redo日志,所以,SELECT语句也有可能会产生Redo日志。
因此,建议在批量加载了数据后,通过运行DBMS_STATS实用程序来收集统计信息,就能自然地完成块清除工作。Oracle提供了一个内部事件(10203事件)可以用来跟踪数据库的块清除操作,可以通过以下命令设置:
(4)Undo表空间 Undo信息存储在Undo段中,Undo段又存储在Undo表空间中。Undo表空间仅用于Undo段(在Undo表空间中不能创建其他段类型,例如表、索引等),只能与单个实例相关联。在任意指定时间,一个给定的实例只能有一个表空间是当前可写Undo表空间。Undo表空间是永久的、本地管理的表空间(具有自动区分配),它们由数据库自动进行管理。
Redo和Undo可以从以下几个方面进行区分,见表。
Oracle Undo段中区有几种状态(DBA_UNDO_EXTENTS的STATUS列):ACTIVE、EXPIRED和UNEXPIRED:
1)ACTIVE表示事物还在活动,该值对应的Undo段的DBA_ROLLBACK_SEGS.STATUS一定是ONLINE状态,一旦没有活动的事务在使用Undo段,那么对应的Undo段就变成OFFLINE状态。ACTIVE状态的Undo区不会被覆盖。
2)EXPIRED表示事务已经提交且超过了UNDO_RETENTION指定时间,该状态可以被覆盖使用。
3)UNEXPIRED表示事务已经提交但是还没有超过UNDO_RETENTION指定时间,该状态可以被覆盖使用。
关于Undo表空间有如下几个参数:
1)UNDO_RETENTION。参数指定已提交的Undo信息要保留多长时间(单位为秒),默认为900s(即15min)。但足该值不是绝对的,也就是说,如果有其他事务需要Undo空间,而Undo空间出现不足时,这些信息仍然会被覆盖。只有当表空间设置为GUARANTEE时,才能确保已提交的数据保留UNDO_RETENTION参数设置的时间。RETENTION GUARANTEE是表空间属性而不是初始化参数,此属性只可使用SQL命令行语句来更改。通过更改Undo表空间来保证保留时间的语法是:
将有保留时间保证的还原表空间返回到其常规设置,请使用以下命令:
查询保留时间状态:
如果设置UNDO_RETENTION为0,那么Oracle启用自动调整UNDO_RETENTION (auto tuning of undo_retention)以满足最长运行查询的需要,在告警日志文件中可以看到如下信息:
可以通过设置“"_undo_autotune"=FALSE”来显式地关闭自动调整UNDO_RETENTION功能。
2)UNDO_MANAGEMENT。参数用于指定Undo数据的管理方式,分为自动Undo管理(AUM,Automatic Undo Management)和手动Undo管理(MUM,Manual Undo Management)。如果要使用AUM,那么必须设置为AUTO;如果要使用MUM,那么必须设置为MANUAL。在使用AUM时,Oracle会使用Undo表空间管理Undo数据;在使用MUM时,Oracle会使用回滚段管理Undo数据。需要注意的是,在使用AUM时,如果没有配置初始化参数UNDO_TABLESPACE,那么Oracle会自动选择第一个可用的Undo表空间存放Undo数据,如果没有可用的Undo表空间,那么Oracle会使用SYSTEM回滚段存放Undo记录,并在告警文件中记录警告。
3)UNDO_TABLESPACE。在使用AUM时,该参数用于指定实例所要使用的Undo表空间。在RAC结构中,因为一个Undo表空间不能由多个实例同时使用,所以必须为每个实例配置一个独立的Undo表空间。
(5)系统回滚段(System Rollback Segment) SYSTEM回滚段创建在系统表空间中,当手工创建数据库后,在创建普通回滚段之前必须首先创建系统回滚段。但正常情况下,系统回滚段主要用于两个方面:一是系统事务,另一个就是延迟回滚段(Deferred Rollback Segment)。
(6)ORA-01555 在告警日志中记录的ORA-01555(snapshot too old,快照过旧)报错信息与下条信息类似:
默认情况,ORA-01555错误发生时不会自动生成跟踪日志文件,但是可以在系统里设置下面的事件,让它在错误发生时同时生成跟踪日志文件:
由于回滚段是循环使用的,当事务提交以后,该事务占用的回滚段事务会被标记为非活动,此时的回滚段空间可以被覆盖重用。那么问题就出现了,如果一个查询需要使用被覆盖的回滚段构造前镜像(Before Image)实现一致性读,那么此时就会出现Oracle著名的ORA-01555(snapshot too old,快照过旧)错误。需要注意的是,ORA-01555错误是一个安全的错误,它不会造成数据丢失或者损坏,只是会让收到该错误的查询无法继续。
ORA-01555错误的另外一个原因是延迟块清除(Delayed Block Cleanout)。当一个查询触发延迟块清除时,Oracle需要去查询回滚段获得该事务的提交SCN。如果事务的前镜像(Before Image)信息已经被覆盖,并且查询SCN也小于回滚段中记录的最小提交SCN,那么Oracle将无从判断查询SCN和事务提交SCN的大小,此时出现延迟块清除会导致ORA-01555错误。
还有一种导致ORA-01555错误的情况出现在使用sqL*Loader直接方式加载(direct=true)数据时,由于不产生Redo和Undo信息,Oracle直接指定CACHED COMMIT SCN进行加载数据,在访问这些数据时,有时会产生ORA-01555错误。
知道了ORA-01555错误产生的原因就可以总结出以下方法来解决ORA-01555错误问题:
1)扩大回滚段。因为回滚段是循环使用的,如果回滚段足够大,那么那些被提交的数据信息就能保存足够长的时间,而那些大事务就可以完成一致性读取。
2)增加UNDO_RETENTION时间。在UNDO_RETENTION规定的时间内,任何其他事务都不能覆盖这些数据。
3)优化相关查询语句,减少一致性读。减少查询语句的一致性读,也可以降低读取不到回滚段数据的风险。
4)减少不必要的事务提交。提交的事务越少,产生的回滚段信息就越少。
5)对大事务指定回滚段。通过以下语句可以指定事务的回滚段:
给大事务指定回滚段,既降低大事务回滚信息覆盖其他事务的回滚信息的几率,又降低了它自身的吲滚信息被覆盖的几率。大事务的存在,往往是ORA-01555错误产生的诱因。
6)使用游标时尽量使用显式游标,并且只在需要的时候打开游标,同时将所有可以在游标外做的操作从游标循环中拿出。当游标打开时,查询就开始了,直到游标关闭。减少游标的打开时间,就减少了ORA-01555错误发生的几率。
7)使用回滚表空间自动管理。回滚表空间自动管理是Oracle 9i后的特性,Oracle自动管理回滚段的创建和回收,并且Oracle 10g中,这一特性大大增强了,而在大型的数据仓库或者报表系统中,会有一些很大的查询作业存在,这时可以考虑使用手动管理,为某些大作业创建单独的回滚段。