技术开发 频道

oracle工作机制

    只有当SQL语句影响的所有行所在的最后一个块被读入DB BUFFER并且重做信息被写入REDO LOG BUFFER(仅是指重做日志缓冲,而非重做日志文件)之后,用户才可以发出COMMIT,COMMIT触发LGRW,但并不强制立即DBWN来释放所有相应的DB BUFFER块上的锁,也就是说有可能出现已COMMIT,但在随后的一段时间内DBWN还在写这条语句涉及的数据块的情形,表头部的行锁,并不是在COMMIT一发出就马上释放,实际上要等到相应的DBWN进程结束才会释放。一个用户请求锁定另一个用户已COMMIT的资源不成功的机会是存在的,从COMMIT到DBWN进程结束之间的时间很短,如果恰巧在这个时间断电,由于COMMIT已触发LGWR进程,所以这些未来得及写入数据文件的改变会在实例重启后由SMON进程根据重做日志文件来前滚。如果未COMMIT就断电,由于DBWN之前触发LGWR,所有DBWN在数据文件上的修改都会被先一步记入重做日志文件,实例重启后,SMON进程再根据重做日志文件来回滚。

    如果用户ROOLBACK,则服务器进程会根据数据文件块和DB BUFFER中块的头部的事务列表和SCN以及回滚段地址找到回滚段中相应的修改前的副本,并且用这些原值来还原当前数据文件中已修改但未提交的改变。如果有多个“前映像”,服务器进程会在一个“前映像”的头部找到“前前映像”的回滚段地址,一直找到同一事务下的最早的一个“前映像”为止。一旦发出了COMMIT,用户就不能ROOLBACK,这使得COMMIT后DBWN进程还没有全部完成的后续动作得到了保障。

    下面我们要提到检查点的作用,当一个全部检查点发生的时候,首先让LGWR进程将REDO LOG BUFFER中的所有缓冲(包含未提交的重做信息)写入重做日志文件,然后让DBWN进程将DB BUFFER中所有已提交的缓冲写入数据文件(不强制写未提交的)。然后更新控制文件和数据文件头部的SCN,表明当前数据库是一致的,如果在发生检点之前断电,并且当时有一个未提交的改变正在进行,实例重启之后,SMON进程将从上一个检查点开始核对这个检查点之后记录在重做日志文件中已提交的和未提交改变,因为DBWN之前会触发LGWR,所以DBWN对数据文件的修改一定会被先记录在重做日志文件中。因此,断电前被DBWN写进数据文件的改变将通过重做日志文件中的记录进行还原,叫做回滚,如果断电时有一个已提交,但DBWN动作还没有完全完成的改变存在,因为已经提交,提交会触发LGWR进程,所以不管DBWN动作是否已完成,该语句将要影响的行及其产生的结果一定已经记录在重做日志文件中了,则实例重启后,SMON进程根据重做日志文件进行前滚。由此可见,实例失败后用于恢复的时间由两个检查点之间的间隔大小来决定,我们可以通个四个参数设置检查点执行的频率,LOG_CHECKPOINT_IMTERVAL决定了两个检查点之间写入重做日志文件的系统物理块的大小,LOG_CHECKPOINT_TIMEOUT决定了两个检查点之间的时间长度,FAST_START_IO_TARGET决定了用于恢复时需要处理的块的大小,FAST_START_MTTR_TARGET直接决定了用于恢复的时间的长短。SMON进程执行的前滚和回滚与用户的回滚是不同的,SMON是根据重做日志文件进行前滚或回滚,而用户的回滚一定是根据回滚段的内容进行回滚的。在这里我们要说一下回滚段存储的数据,假如是delete操作,则回滚段将会记录整个行的数据,假如是update,则回滚段只记录被修改了的字段的变化前的数据(前映像),也就是没有被修改的字段是不会被记录的,假如是insert,则回滚段只记录插入记录的rowid。这样假如事务提交,那回滚段中简单标记该事务已经提交;假如是回退,则如果操作是是delete,回退的时候把回滚段中数据重新写回数据块,操作如果是update,则把变化前数据修改回去,操作如果是insert,则根据记录的rowid 把该记录删除。

    下面我们要讲DBWN如何来写数据文件,在写数据文件前首先要找到可写的空闲数据块,ORACLE中空闲数据块可以通过FREELIST或BITMAP来维护,它们位于一个段的头部用来标识当前段中哪些数据块可以进行INSERT。在本地管理表空间中ORACLE自动管理分配给段的区的大小,区的分配信息存储在组成表空间的数据文件的头部,而数据字典管理的表空间用户可以在创建时决定区的大小,并且区的分配信息是存储在数据字典中的,只在本地管理的表空间中才能选用段自动管理,采用自动段空间管理的本地管理表空间中的段中的空闲数据块的信息就存放在段的头部并且使用位图来管理,采用手动管理的本地管理表空间中的段和数据字典管理的表空间中的段中的空闲数据块的管理都使用位于段头部的空闲列表来管理,空闲列表的工作方式:首先一个空的数据块被加入空闲列表,当其中空闲空间小于PCTFREE设置的值之后,这个块从空闲列表删除,当这个块中的内容降至PCTUSED设置的值之下后,这个数据块被再次加入空闲列表,位于空闲列表中的数据块都是可以向其中INSERT的块,当一个块移出了空闲列表,但只要其中还有保留空间就可以进行UPDATE,当对其中一行UPDATE一个大数据时,如果当前块不能完全放下整个行,只会把整个行迁移到一个新的数据块,并在原块位置留下一个指向新块的指针,这叫行迁移。如果一个数据块可以INSERT,当插入一个当前块装不下的行时,这个行会溢出到两个或两个几上的块中,这叫行链接。如果用户的动作是INSERT 则服务器进程会先锁定FREELIST,然后找到空闲块的地址,再释放FREELIST,当多个服务器进程同时想要锁定FREELIST时即发生FREELIST的争用,可以在非采用自动段空间管理的表空间中创建表时指定FREELIST的个数,默认为1,如果是在采用自动段空间管理的表空间中创建表,即使指定了FREELIST也会被忽略,因为此时将使用BITMAP而不是FREELIST来管理段中的空闲空间。如果用户动作是UPDATE服务器进程将不会使用到FREELIST和BITMAP,因为不要去寻找一个空闲块,而使用锁的队列。

    下面来讲一下ORACLE锁的机制,ORACLE分锁存器和锁两种。锁存器是用来保护对内存结构的访问,比如对DB BUFFER中块的锁存器申请,只有在DBWN完成后,这些DB BUFFER块被解锁。然后用于其它的申请。锁存器不可以在进程间共享,锁存器的申请要么成功要么失败,没有锁存器申请队列。主要的锁存器有SHARED POOL锁存器,LIBRARY CACHE锁存器,CACHE BUFFERS LRU CHAIN锁存器,CACHE BUFFERS CHAINS 锁存器,REDO ALLOCATION 锁存器,REDO COPY 锁存器。ORACLE的锁是用来保护数据访问的,锁的限制比锁存器要更宽松,比如,多个用户在修改同一表的不同行时,可以共享一个表上的一个锁,锁的申请可以按照被申请的顺序来排队等候,然后依次应用,这种排队机制叫做队列(ENPUEUE),如果两个服务器进程试图对同一表的同一行进行加锁,则都进入锁的申请队列,先进的加锁成功,后面的进程要等待,直到前一个进程解锁才可以加锁,这叫做锁的争用,而且一旦加锁成功,这个锁将一直保持到用户发出COMMIT或ROOLBACK命令为止。如果两个用户锁定各自的一行并请求对方锁定的行的时候将发生无限期等待即死锁,死锁的发生都是由于锁的争用而不是锁存器的争用引起的,ORACLE在遇到死锁时,自动释放其中一个用户的锁并回滚此用户的改变。正常情况下发生锁的争用时,数据的最终保存结果由SCN来决定哪个进程的更改被最终保存。两个用户的服务器进程在申请同一表的多个行的锁的时候是可以交错进入锁的申请队列的。只有其中发生争用才会进行等待。创建表时指定的MAXTRANS参数决了,表中的一个数据块最多可以被几个事务同时锁定。

    下面是几个关于回滚段和死锁的事例:

    有表:Test (id number(10)) 有记录1000000条

    一,大SELECT,小UPDATE
A会话----Select * from test;----设scn=101----执行时间09:10:11
B会话-----Update test set id=9999999 where id=1000000----设scn=102-----执行时间09:10:12

    我们会发现B会话会在A会话前完成,A会话中显示的ID=100000是从回滚段中读取的,因为A会话在读到ID=1000000所在的BLOCK时发现BLOCK上有事务信息,因此要从回滚段中读,如果UPDATE在SELECT读到此BLOCK之前已经COMMIT,则SELECT 读到此BLOCK时发现其BLOCK上没有事务信息,但是会发现其BLICK的SCN比SELECT自己的SCN大,因此也会从回滚段中读取。因此是否从回滚段读一是看是否有事务信息二是比较SCN大小。如果B会话在A会话结束前连续多次对同一条记录UPDATE并COMMIT,那么在回滚段中将记录多个“前映像”,而每个“前映像”中不但包括了原BLOCK的数据和SCN也记录了“前前映像”的回滚段地址,因此A会话在查询到被UPDATE过的BLOCK时,会根据BLOCK记录的回滚段的地址,找到回滚段中的“前映像”,发现这个“前映像”的SCN也比自己的大,因此将根据这个“前映像”中记录的“前前映像”的回滚段地址,在回滚段中找到“前前映像”,再与这个“前前映像”比较SCN,如果比自己小就读取,如果还比自己大,则重复以上步骤,直到找到比自己SCN小的“前…前映像”为止,如果找不到,就会报ORA-01555快照太旧这个错误。

    二、大UPDATE,小SELECT

    A会话----Update test set id=1;----设scn=101----执行时间09:10:11
B会话-----select * from test where id=1000000----设scn=102-----执行时间09:10:12

    我们会发现B会话会在A会话前完成,B会话中显示的ID=1000000是从BLOCK中直接读取的,因为B会话在读到ID=1000000所在的BLOCK时,A会话还没有来得及对其锁定,因此B会话既不会发现BLOCK上有事务信息,也不会发现BLOCK上的SCN比SELECT的大,因此会从BLOCK中直接读取,如果SELECT在UPDATE锁定此BLOCK后才发出,B会话读到此BLOCK时发现其BLOCK上有事务信息,因此会从回滚段中读取。

    三、大UPDATE,小UPDATE

    A会话----Update test set id=1;----设scn=101----执行时间09:10:11
B会话1-----Update test set id=999999 where id=1000000----设scn=102-----执行时间09:10:12
B会话2----- select * from test where id=2----设scn=103-----执行时间09:10:14
B会话3----- update test set id=3 where id=2----设scn=104-----执行时间09:10:15

    我们会发现B会话1会完成,A会话将一直等待,因为B会话1会先于A会话锁定ID=1000000所在的BLOCK,并改写头部的事务信息,A会话在试图锁定此BLOCK时,发现其上有事务信息,将会一直等待B会话1事务结束后再行锁定, B会话2查询到的ID=2是从回滚段中读取的而不是从BLOCK中直接读出来的。因为A会话已将ID=2的BLOCK锁定,并写入了回滚段,从B会话3可以证明这一点,B会话3发出后,B会话3会收到死锁的信息,死锁的原因是A会话在等待B会话对ID=1000000所在的BLOCK解锁,现在B会话又在等待A会话对ID=2所在的BLOCK解锁,因此形成死锁,因此证明ID=2所在的BLOCK已被A会话锁定,然后A会话也会收到死锁的信息

0
相关文章