1回滚与撤销
Refer:《深入解析oracle》by eygle
(1) 为了多用户的读一致性和能回退事务,oracle提供了为修改的数据保存修改之前的旧值。
(2) Redo:保证在故障时事务可以恢复
Undo:保证事务可以被回滚或撤销
(3) 9i之前,oracle提供回滚段(rollback)来撤销数据;之后,oracle使用undo表空间来管理。
(4) 下面这个例子是介绍9i前,是如何保证可以回滚的。
Update emp set sal=4000 where empno=7788;
简单看一下这个语句的执行过程:
A:检查empno=7788记录在database buffer cache中是否存在;否,则读取到database buffer cache中。
B:在回滚表空间的相应回滚段事务表上分配事物槽,这个操作需要记录redo信息。
C:在回滚段读入或者在database buffer cache中创建sal=3000的旧值,这需要产生redo信息并记入redo log buffer。
【B,C这两步保证了事务的可回滚性,此后事务的修改才能进行】
D:修改sal=4000,这是update的数据变更,需要记录redo log buffer。
F:当用户提交时,会在redo log buffer记录提交信息,并在回滚段标记该事务为非激活(inactive)。
(5) 如果用户回滚(rollback)事务,则oracle需要从回滚段中把旧值读取出来,修改database buffer cache,完成rollback,这个过程本身会产生redo,so回滚是expensive。
(6) 回滚段在undo表空间中分配,其作用:回退事务,事务恢复,提供读一致性。
对于DML:
Insert:回滚段只需记录插入的记录的rowid;
Update:回滚段只需记录被更新字段的旧值;
Delete:oracle必须记录整行的数据
所以,对产生undo的情况看,delete产生的undo最多,推荐对大规模数据删除操作时,分批删除,分次提交,以减少对回滚段的占用和冲击。
(7) oracle区别于其他数据库的一个重要的特征:
通过多版本架构,oracle实现了读取和写入的分离,使得写入不阻塞读取,读取不阻塞写入。
多版本架构是通过一致性读来实现的。
假定scott的薪水为3000:
A:t1时刻我们在session 1 查询可以得到3000;
B:t2时刻session 2进行update,但未提交(此时数据在database buffer cache中已经修改,该buffer为dirty)
C:t3时刻session 1再次查询,注意此时,oracle不会允许其他用户看到未提交的数据,oracle需要通过回滚段记录的旧值进行一致性读,将3000恢复出来给用户,这是一致性读的作用;
D:t4时刻,session 2提交该更改,此时数据修改已被永久化;
F:t5时刻,其他用户再次查询;将会看到变化后的数据,也就是4000.
Notice:
A:每个数据块头部都会记录一个提交的SCN,当数据更改提交后,提交后,提交SCN同时被修改,这个SCN在查询时可以用来进行一致性读的判断。
B:上图中,假定查询开始的时间为t1,则查询获取的数据块中,如果数据块的提交SCN小于t1,则oracle接收该数据【session 2便是这种情况】;如果提交SCN大于t1或者数据被锁定修改尚未记录提交SCN,则oracle需要通过回滚段构造前镜像来返回结果【session 1便是这种情景】,这就是一致性读的本质含义。
(8)9i之后,oracle引入了undo表空间,若选择自动的undo表空间管理,则oracle会动态创建和释放回滚段,自动为事务指定回滚段。
命令:Show parameter undo
里面有个参数undo_retention与ORA_01555错误有关。
这个参数可调:alter system set undo_retention=**;
Undo_retention:当事务提交后undo信息保留的时间(秒);
10g对于undo增加了guarantee控制:
Alter tablespace undotbs1 retention guarantee | noguarantee
区别:
若把undo表空间自动扩展属性取消:
Alter database datafile ‘/u01/app/oracle/product/10.2.0/oradata/undotbs’
Autoextend off;
进行循环删除数据
在guarantee设置下,会出现ORA-30036错误;
在noguarantee设置下,则可以顺利完成,因为oracle启动自动调整以满足最长运行查询的需要。
(9) ORA-01555成因与解决
A:成因:
I) 由于回滚段是循环使用的,当事务提交后,该事务占用的回滚段事务表会被标识为inactive,回滚段表空间可以被覆盖重用。但是,当一个查询需要使用被覆盖的回滚段构造前镜像实现一致性读,那么就会出现著名的ORA-01555错误。
II) 未完待续。。。。。。。。。。
2 大师,你好!
我在《深入浅出oracle》一书的167页的一句话不清楚,原话是:如果读取的block不满足读一致性需求,则server进程需要通过当前block版本和回滚段构造前镜像返回给用户。‘读一致性’是怎么判断出来的,被修改的数据要作排他锁,别人怎么读取那,盼回复,谢谢!
根据SCN来判断阿。
3读一致性(Read Consistency),这是数据库的一个关键特性,可以确保用户在查询期间看到一致的数据。也就是说,当一个会话正在修改数据时,其他的会话将看不到该会话未提交的修改。
4事务的内部流程:
1)首先,当一个事务开始时,需要在回滚段事务表上分配一个事务槽;
2)在数据块头部获取一个ITL事务槽,该事务槽指向回滚段头的事务槽
3)在修改数据前,需要记录前镜像信息,这个信息以undo recode的形式存储在回滚段中,回滚段头事务槽指向该记录;
4)锁定修改行,修改行锁定位指向ITL事务槽。
5)数据修改可以进行
5 Oracle数据库一致性读的原理
在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号)。
注:ITL(Interested Transaction List)是Oracle数据块内部的一个组成部分,用来记录该块所有发生的事务,一个itl可以看作是一个记录,在一个时间,可以记录一个事务(包括提交或者未提交事务)。当然,如果这个事务已经提交,那么这个itl的位置就可以被反复使用了,因为itl类似记录,所以,有的时候也叫itl槽位。
服务器进程在扫描表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-1555,也就是snapshot too old的错误。
回滚事务则是在执行DML以后,发出rollback命令撤销DML所作的变化。Oracle利用记录在ITL槽里记录的undo块的地址找到该undo块,然后从中取出变化前的值,并放入数据块中,从而对事务所作的变化进行回滚。
实例恢复则是在SMON进程完成前滚并打开数据库以后发生。SMON进程会去查看undo segment头部(所谓头部就是undo segment里的第一个数据块)记录的事务表(每个事务在使用undo块时,首先要在该undo块所在的undo segment的头部记录一个条目,该条目里记录了该事务相关的信息,其中包括是否提交等),将其中既没有提交也没有回滚,而是在实例崩溃时被异常终止的事务全部回滚。
6缺省时,oracle表级锁为:S锁
行级锁为:X锁
7 一致性读
当会话发出一条SQL查询,将当前时间的SCN号记录下来,当进程扫描到表T的数据块,再与该块头部的ITL槽(事务槽)的SCN号比较,如果发现该块SCN号较小,则该块没有被更新过所以可用;如果该块SCN号较大,则该块被更新过,要借助UNDO块了,该块的ITL槽会记录了对应的UNDO块的地址,就取出对应的UNDO块,如果发现该UNDO块的ITL槽的SCN号也较大,证明也不可用,则在该块的ITL槽寻找再上一个块的UNDO块地址,层层递归,最终找到SCN号比发出查询的SCN号小的UNDO块。
因为UNDO块记录的是逻辑改变的值,例如INSERT操作记录在UNDO块就是DELETE,过程中,全部UNDO块一个一个地找出来,这有点增量改变的意思,再全部组成一起构成CR块以交给用户使用。
ORA-1555 snapshot too old错误,须用UNDO块的时候,发现UNDO块被其他事务覆盖了,找不回了而出现的。
8为避免ORA-1555快照太旧的错误,出现了undo_retention参数,表示当事务提交或回滚后,该事务所使用的undo块里的数据需要保留多长时间,秒为单位。当保留的时间超过undo_retention所指定的时间以后,该undo块才能被其他事务覆盖。
默认情况下,ORACLE10g会每隔30秒就收集统计信息来自动调整undo retention,如果我们设定undo_retention为0,或不设定,则启动此种模式,900秒为最低值;如果我们手动设定了undo_retention,则用我们指定的时间为undo保留的时间;
alter tablespace undoabc retention guarantee;
就保证了undo块一定能保留那么多时间。
alter tablespace undoabc retention noguarantee;
取消。
9不可重复读的重点在于修改:同样的条件,你读取过的数据,再次读取出来发现值不一样了
幻读的重点在于新增或者删除:同样的条件,第 1 次和第 2 次读出来的记录数不一样