Oracle的一致性读原理

Oracle的一致性读原理


                                                                                                                       图为oracle执行查询语句的过程

在Oracle数据库中,undo主要有三个大作用:第一是提供一致性读(Consistent Read)、第二是回滚事物(Rollback Transaction)以及第三实例恢复(instance Recovery)。总结起来它使得数据库回到过去成为可能。

一致性读是相对脏读(Dirty Read)而言的。当一张表有几万条记录时,取得所有记录可能需要很长时间,当这个事务对一张表访问时,另一个用户对未访问的数据进行了修改,而第一个用户返回的结果为事务修改后的记录,这时说明了数据库发生了脏读,而返回的结果为事务修改前的记录时,这说明了数据库发生了一致性读。

假设有一张10000条记录的表T,取得所有的记录假设需要20分钟的时间。我们假设当时间为9点整的时候,用户A发出对这张表的所有数据的查询语句:select  *  from  T,该语句假设在9:20执行完毕。当用户A执行该语句之后10分钟的时间9:10分,另外一个用户B发出了一条delete命令,将T表的最后一条记录删除了并且提交了。

此时我们查看返回的记录数,如果返回9999条记录,则说明发生了脏读,如果仍然返回了10000条记录时,则说明发生了一致性读。

在9点钟的时候发一条出查询语句select * form t,因为查询输出I/O相对较慢的原因花费20分钟完才能成所有的记录检索。由于undo的存在Oracle数据库可以实现一致性读,并且该一致性读是在没有阻塞用户的DML的前提下实现的。

看一下数据库的undo数据是如何实现一致性读的。举个例子,用户在9点钟的时候发出了一条查询语句时,服务器进程会将9点那个时间上的SCN号记录下来,假设该SCN号为900。那么9点整的时刻的SCN号900一定大于等于记录在所有数据块头部的ITL槽中的SCN号。

       服务器进程在扫描表T的的数据块的时,会把扫描到的数据库头部的ITL槽中的SCN号与900作比较,哪个更大。如果数据块头部的SCN号比900小,说明该数据块在9点以后没有被更新,可以直接读取其中的数据;当数据块ITL槽的SCN号比900要大,则说明这个数据块在9点以后被更新了,该快里的数据已经不是9点那个时间点的数据了,需要借助于undo块中的数据。

       9点10分,B用户更新了表T的最后一条记录,当被更新的记录属于A数据块时,这时A号数据块头的ITL槽的SCN号就被改为这个时间的SCN号,假设这个SCN号为910。当A用户的服务器进程扫描到被更新的数据块A时,发现它的ITL槽中的SCN号910大于发出查询时的SCN号900时,说明了这个数据块在A用户查询操作后已经被更新了。于是服务器进程到A号块的头部,找到910所在的ITL槽,在这个槽中记录了对应的undo块的地址,然后根据该地址找到undo块,将undo块中的被修改前的数据取出,再结合A号块里的数据行,从而还原出9点时的那个时间点的数据块内容,这样的数据块是一种一致性读的数据块,对于delete来说,其undo信息就是insert,也就是说该构建出来的一致性读块中就插入了被删除的那条记录。随后,服务器进程扫描该一致性读块,从而返回delete操作之前的记录。

       考虑这样一个问题,假设在9点10分B用户删除了最后一条记录并提交以后,紧跟着9点15分,C用户在同一个数据块里插入了2条记录。因为我们知道,事务需要使用ITL槽,只要该事务提交或者回滚,该ITL槽就能够被重用。也就是说该ITL槽里的记录的SCN号已经是915了,而不是910了,这时,ITL槽被覆盖了,Oracle的服务器进程怎样找回最初的数据呢?

原来oracle在记录undo数据的时候,不仅记录了改变前的数据,还记录了改变前的数据所在的数据块头部的ITL信息。因此9点10分B用户删除记录时(位于A号块里),假设在9点时A号块的ITL信息为[Undo_block0/850],则Oracle会将改变前的数据也就是insert语句放到undo块中(假设该undo块地址为undo_block1)里,同时在该undo块里记录删除前ITL信息(也就是[undo_block0/850])。删除记录后,该A号块里的ITL信息就变成了[undo_block/910];到了9点15分,C用户又在A号块里插入了两条记录,则Oracle将插入前的数据(也就是delete两条记录)放到undo_块(假设undo块的地址为undo_block2)里。并将9点15分时的ITL槽的信息(也就是[undo_block1/910])也记录到该undo块里。插入两条记录后,该A号块的ITL槽的信息改为[undo_block2/915]。

当执行查询的服务器进程扫描到A号块时,发现SCN号为915大于SCN为900,于是oracle到ITL槽中中指定的undo_block2处找到该undo块。发现该undo块里记录的ITL信息为[undo_block1/910],其中的SCN为910仍然大于SCN为900,于是服务器进程继续根据ITL中记录的undo_block1,找到该undo块。发现该undo块里记录的ITL信息为[undo_block0/850],这时ITL里的SCN号850小于查询时发出的SCN号为900,说明这时undo块包含了合适的undo信息,于是服务器进程不再去找下去,而是将A号块、undo_block2以及undo_block1的数据结合起来,构建构建这个一致性读块。将当前A号的数据复制到一致性读块里,然后在一致性块里先回退到9点15分的事务通过undo_block2,就是在一致性读块里删除两条记录,然后再回退到9点10分的事务通过undo_block1,也就是在一致性读块里插入被删除的记录,从而构建出9点中的数据通过undo,就这样Oracle通过undo块来实现回退的过程。通过查找整个undo链表来判断出ITL槽里的SCN号最近的小于等于发出语句的SCN号,这时执行undo块中的数据,直到SCN号小于等于发出语句的SCN号来实现回退的过程。

当然只要undo的大小足够大的时候,undo块里的记录的SCN号要比上一个undo块里的记录的SCN号要小。但是在查找的过程可能会发现当前undo块里记录的ITL槽的SCN号比上一个undo块里记录的SCN号还要大。这种情况下说明由于事务被提交或者回滚,导致当前找到的undo块里的数据已经被其他事务覆盖了,于是我们无法找出小于等于发出查询的那个时间点的SCN号,这时Oracle就会抛出一个ora-01555的错误snapshot too old的错误。

回滚事务是在执行DML语句以后,通过rollback命令来撤销DML语句执行的结果所做的变化,oracle利用undo块里的ITL槽中SCN记录来确定数据库执行的系统时间点,通过这个时间点找到在这个时间点到当前时间点对数据库中所有数据块的undo块信息,undo块中记录了这个DML操作执行的相反操作,当发出回退命令时执行这些undo块信息的操作内容,使得oracle回退到执行这条DML语句改变之前的时间点。

实例恢复则是在SMON进程完成前滚打开数据库以后发生的。SMON进程会去查看undo segment头部(就是undo segment里的第一个数据块)记录的事务表,当每个事务使用undo块时,首先会在这个undo块所在的undo segment的头部记录了一个条目,这个条目里记录了该事务的相关信息,其中就包括了事务是否提交的信息,当实例崩溃时被异常终止的事务通过undo segment找到这个事务的undo块信息通过SMON将事务全部回滚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值