ORACLE的工作机制

原帖地址:http://blog.oracle.com.cn/html/12/34012-4546.html

ORACLE的工作机制-1
肖亚峰(xyf_tck)
 
我们从一个用户请求开始讲,ORACLE的简要的工作机制是怎样的,首先一个用户进程发出一个连接请求,如果使用的是主机命名或者是本地服务命中的主机名使用的是机器名(IP地址),那么这个请求都会通过DNS服务器或HOST文件的服务名解析然后传送到ORACLE监听进程,监听进程接收到用户请求后会采取两种方式来处理这个用户请求,下面我们分专用服务器和共享服务器分别采用这两种方式时的情况来讲:
专用服务器模式下:一种方式是监听进程接收到用户进程请求后,产生一个新的专用服务器进程,并且将对用户进程的所有控制信息传给此服务器进程,也就是说新建的服务器进程继承了监听进程的信息,然后这个服务器进程给用户进程发一个RESEND包,通知用户进程可以开始给它发信息了,用户进程给这个新建的服务器进程发一个CONNECT包,服务器进程再以ACCEPT包回应用户进程,至此,用户进程正式与服务器进程确定连接。我们把这种连接叫做HAND-OFF连接,也叫转换连接。另一种方式是监听进程接收到用户进程的请求后产生一个新的专用服务器进程,这个服务器进程选用一个TCP/IP端口来控制与用户进程的交互,然后将此信息回传给监听进程,监听进程再将此信息传给用户进程,用户进程使用这个端口给服务器进程发送一个CONNECT包,服务器进程再给用户进程发送一个ACCEPT包,至此,用户进程可以正式向服务器进程发送信息了。这种方式我们叫做重定向连接。HAND-OFF连接需要系统平台具有进程继承的能力,为了使WINDOWS NT/2000支持HAND-OFF必须在HKEY_LOCAL_MACHINE>SOFTWARE>ORACLE>HOMEX中设置USE_SHARED_SOCKET
共享服务器模式下:只有重定向连接的方式,工作方式是监听进程接收到用户进程的请求后产生一个新的调度进程,这个调度进程选用一个TCP/IP端口来控制与用户进程的交互,然后将此信息回传给监听进程,监听进程再将此信息传给用户进程,用户进程使用这个端口给调度进程发送一个CONNECT包,调度进程再给用户进程发送一个ACCEPT包,至此,用户进程可以正式向调度进程发送信息了。可以通过设置MAX_DISPIATCHERS这个参数来确定调度进程的最大数目,如果调度进程的个数已经达到了最大,或者已有的调度进程不是满负荷,监听进程将不再创建新的调度进程,而是让其中一个调度进程选用一个TCP/IP端口来与此用户进程交互。调度进程每接收一个用户进程请求都会在监听进程处作一个登记,以便监听进程能够均衡每个调度进程的负荷,所有的用户进程请求将分别在有限的调度进程中排队,所有调度进程再顺序的把各自队列中的部分用户进程请求放入同一个请求队列,等候多个ORACLE的共享服务器进程进行处理(可以通过SHARED_SERVERS参数设置共享服务器进程的个数),也就是说所有的调度进程共享同一个请求队列,共享服务器模式下一个实例只有一个请求队列,共享服务器进程处理完用户进程的请求后将根据用户进程请求取自不同的调度进程将返回结果放入不同的响应队列,也就是说有多少调度进程就有多少响应队列,然后各个调度进程从各自的响应队列中将结果取出再返回给用户进程。
以上我们讲完了用户与ORACLE的连接方式,下面我们要讲ORACLE服务器进程如何处理用户进程的请求,当一个用户进程发出了一条SQL语句:UPDATE TABBLEA SET SALARY=SALARY*2;首先服务器进程将对该语句进行检查语句有效性的语法检查和确保语句能够正常运行的语义检查,首先检查该语句的语法的正确性(语法检查),接着对照数据字典对语句中涉及的表、索引、视图等对象及用户的权限进行检查(语义检查),如果以上任一检查没有通过,就返回一个错误,但不会明确的指出是语法检查出错还是语义检查出错,它只会返回一个ORA-*****的错误码。如果检查通过以后,服务器进程把这条语句的字符转换成ASCII等效数字码(注意SQL中使用*是个例外,如果表的字段改变了,同样是SELECT * FROM TABLEA转换成的ASCII是不同的,其实它在语义检查时就明确的变成了操作具体字段的SQL语句了),接着这个ASCII码被传递给一个HASH函数,并返回一个HASH值,服务器进程将到SHARED POOL的共享PL/SQL区去查找是否存在同样的HASH值,如果存在,服务器进程将使用这条语句已高速缓存在SHARED POOL中的已分析过的版本来执行(软解析),如果不存在,则必须进行以下两个步骤:语句的优化(生成执行计划)生成执行编码:服务器进程根据ORACLE选用的优化模式以及数据字典中是否存在相应对象的统计数据和是否使用了存储大纲来生成一个执行计划或从存储大纲中选用一个执行计划,最后再生成一个编译代码(硬解析)。(这里要注意的是,语法语义分析在前,计算HASH_VALUE在后,算出HASH_VALUE后只要找到相同的HASH_VALUE就使用这条缓存执行计划,语义分析在前确保了用户的使用权限等问题,不存在算出HASH_VALUE,再找到相同HASH_VALUE缓存执行计划而不能使用的情况。也不是先算HASH_VALUE,然后找缓存执行计划,找到后再语义检查这个步骤也是错的)ORACLE将这条语句的本身实际文本、HASH值、编译代码、与此语句相关联的任何统计数据和该语句的执行计划缓存在SHARED POOL的共享PL/SQL区。V$librarycache中的几个参数解释Pins: (Execution)SQL实际执行的次数,不包括用户提交的语法语义检查失败的SQLReloads: (Parse)未找到相同HASH_VALUE的次数,即必须进行硬解析的次数。Invalidations: (Parse)因对象更改,使得所有引用这个对象的缓存执行计划失效而必须再次硬解析的次数。只要DDL更改了一个对象,所有与此有关的缓存在共享池中执行计划都将立即失效,它的失效不是在下次执行SQL时才发现其失效,而是DDL更改对象后立即就失效。主要表现在DDL发生后v$sqlHASH_VALUE仍保持不变,但PLAN_HASH_VALUE立即变为0,再次运行SQL语句时则会向v$sql插入一条新的缓冲记录HASH_VALUEPLAN_HASH_VALUE都重新计算。原来的缓冲记录仍然还存在。
 
服务器进程通过SHARED POOL锁存器来申请可以向哪些共享PL/SQL区中缓存这些内容,也就是说被SHARED POOL锁存器锁定的PL/SQL区中的块不可被覆盖,因为这些块可能正在被其它进程所使用。在SQL分析阶段将用到LIBRARY CACHE,从数据字典中核对表、索引、视图及用户的权限的时候,需要将数据字典从磁盘读入LIBRARY CACHE,因此,在读入之前也要使用LIBRARY CACHE锁存器来申请用于缓存数据字典。

 

ORACLE的工作机制-2
肖亚峰(xyf_tck)
 
生成编译代码之后,接着下一步服务器进程要准备开始更新数据,服务器进程将到 DB BUFFER 中查找是否有相关对象的缓存数据,下面分两个可能进行解释:
如果没有,服务器进程将在表头部请求一些行锁,如果成功加锁,服务器进程将从数据文件中读入这些行所在的第一个数据块 (db block) DB BLOCK ORACLE 的最小操作单元,即使你想要的数据只是 DB BLOCK 中很多行中的一行或几行, ORACLE 也会把这个 DB BLOCK 中的所有行都读入 DB BUFFER 中)放入 DB BUFFER 中空闲的区域或者覆盖已被挤出 LRU 列表的非脏数据块缓冲区,并且排列在 LRU 列表的头部,如果这些非脏数据缓冲区写完也不能满足新数据的请求时,会立即触发 DBWN 进程将脏数据列表中指向的缓冲块写入数据文件,并且清洗掉这些缓冲区,来腾出空间缓冲新读入的数据,也就是在放入 DB BUFFER 之前也是要先申请 DB BUFFER 中的锁存器,成功锁定后,再写入 DB BUFFER ,然后把这个块的头部事务列表及 SCN 信息及被影响的行数据原值写入回滚段中,以便 ORACLE ROLLBACK 时可以利用当前数据块和回滚段重构数据块的 " 前映像 " 或递归重构出 " 前…前映像 " 来实现读一致性。(回滚段可以存储在专门的回滚表空间中,这个表空间由一个或多个物理文件组成,并专用于回滚表空间,回滚段也可在其它表空间中的数据文件中开辟。)然后在 LOG BUFFER 中生成日志,服务器程将该语句影响的被读入 DB BUFFER 块中的这些行的 ROWID 及将要更新的原值和新值及 SCN 等信息 , 以及回滚段的修改信息 ( 即对某某回滚段地址进行了什么修改 ) 逐条的 写入 REDO LOG BUFFER ,在写入 REDO LOG BUFFER 之前也是先请求 REDO LOG BUFFER 块的锁存器,成功锁定之后才开始把 REDOLOG 写入 REDOLOG BUFFER 。当写入达到 REDO LOG BUFFER 大小的三分之一或写入量达到 1M 或超过三秒后或发生检查点时或者 COMMIT 时或者 DBWN 之前触发 LGWR 进程, LGWR 将把 REDO LOG BUFFER 中的数据写入磁盘上的重做日志文件,已被写入重做日志文件的 REDO LOG BUFFER 中的块上的锁存器被释放,并可被后来写入的信息所覆盖。回滚段其实也有 BUFFER (在 DB BUFFER 中开辟),回滚段 BUFFER 中的内容是最早向磁盘上回滚段中写的,写完这些才会生成日志 BUFFER 中的内容,原因是日志中必须要记录回滚段的新旧变化以便在恢复时从日志中的记录的回滚段新旧变化对回滚段再次重写,记住, REDO 光是对数据文件依据日志文件重写,也要依据日志文件对回滚段重写,而且重写回滚段要先于重写数据文件,要理解 REDO 就是重来一遍,所谓重来一遍就要跟正常的的先后顺利一样重做一遍(正常的操作中的顺序就是先读入 DB BUFFER ,写回滚段 buffer, 后写回滚段,后写日志 BUFFER ,后改写 DB BUFFER ,后写日志最后写数据文件)区别是 REDO 时不用再记日志了,这样解释后相信大家应该理解为什么日志中也必须要记录回滚段的信息了,只有这样才可以对正常操作中的一个 ROLLBACK 动作进行恢复,即在 REDO 过程中利用即时重写的数据块和回滚段重构出一个当时适用的前镜像来 rollback 。当一个重做日志文件写满后, LGWR 将切换到下一个重做日志文件,重做日志文件也是循环工作方式。如果是归档模式,归档进程还将前一个写满的重做日志进程写入归档日志文件。当 DB BUFFER 改写之后,服务器进程在脏数据列表中建立一条指向此 DB BUFFER 缓冲块的指针。接着服务器进程会从数据文件读入第二个数据块 (db block) 重复以上读入,建立回滚段,写 LOG BUFFER ,改写 DB BUFFER ,放入脏列表的动作,当脏数据列表达到一定长度时, DBWN 进程将脏数据列表中指向的缓冲块全部写入数据文件,也就是释放加在这些 DB BUFER 块上的锁存器,并在修改相应块的头部的 SCN 号(一次 UPDATE 操作只对应一个 SCN )。前面说过 DBWN 动作之前会先触发 LGWR ,这用以确保写入数据文件的改变首先会被记录在日志文件中。实际上 ORACLE 可以从数据文件中一次读入多个块放入 DB BUFFER ,然后再对这些块建回滚段、再记日志等等,也就是每次操作的对象是 DB BLOCK 的复数,而不仅限于一次操作一个 DB BLOCK ,可以通过参数 DB_FILE_MULTIBLOCK_READ_COUNT 来设置一次读入的块的个数。注意,不管是否提交,用户的所有更改都会被记录在日志文件中,用户级回滚的动作( rollback )没有对应的 COMMIT SCN 。在密集事务的情况下 ,LGWR 可以把多个 COMMIT 产生的 REDO 条目批量写入 REDO LOG FILE ,但每个COMMIT之间有十分之一秒的间隔,且会产生不同的COMMIT SCN。 LGWR 正常情况下是一个休眠进程 , 会被一定的条件触发 , 唤醒 , 比如 COMMIT 就是一个唤醒条件,一旦 LGWR 被唤醒, LGWR 将把唤醒时间点之前 LOG BUFFER 中产生的所有内容 ( 从上次 LGWR 唤醒后到本次唤醒前之间写入 LOG BUFFER 的内容 ) 写入 LOG FILE ,直到 LGWR 完成后, LGWR 才可以被再次触发,在 LGWR 触发到完成期间所有对数据库的操作仍然可以不间断的加入 LOG BUFFER 。在这段时间内, LGWR 不再接收其它条件的触发,比如紧跟前一个 COMMIT 之后的其它 COMMIT (复数)都要等待 LGWR 完成后才可以再次触发 LGWR ,并在 LGWR 下次被触发时,将积累的 REDO BUFFER 条目一次性写入 REDO LOG ,后继的 COMMIT 不会单个单个的触发 LGWR
如果要查找的数据已缓存,则根据用户的 SQL 操作类型决定如何操作,如果是 SELECT 则查看 DB BUFFER 块的头部是否有事务,如果有,将利用回滚段进行重构出一致性块再读取,如果没有则比较 SELECT SCN DB BUFFER 块头部的 SCN 如果比自己大,仍然同上,如果比自己小则认这是一个非脏缓存,可以直接从这个 DB BUFFER 块中读取。如果是 UPDATE 则即使在 DB BUFFER 中找到一个没有事务,而且 SCN 比自己小的非脏缓存数据块,服务器进程仍然要到表的头部对这条记录申请加锁,加锁成功则进行后续动作,如果不成功,则要等待前面的进程解锁后才能进行动作。
ORACLE的工作机制-3
肖亚峰(xyf_tck)
 
只有当SQL语句影响的所有行所在的最后一个块被读入DB BUFFER并且重做信息被写入REDO LOG BUFFER(仅是指重做日志缓冲,而非重做日志文件)之后,用户才可以发出COMMITCOMMIT触发LGRW,但并不强制立即DBWN来释放所有相应的DB BUFFER块上的锁,也就是说有可能出现已COMMIT,但在随后的一段时间内DBWN还在写这条语句涉及的数据块的情形,表头部的行锁,并不是在COMMIT一发出就马上释放,实际上要等到相应的DBWN进程结束才会释放。一个用户请求锁定另一个用户已COMMIT的资源不成功的机会是存在的COMMIT发出后会将回滚段中的"前映像"标识为已提交.DML语句会产生一个SCN号,DBWN触发时写入到数据块的头部,COMMIT时也会产生一个SCN号,也会被写入数据块的头部。在数据块的头部只存储一个最新的SCN号,COMMIT之后这个事务插槽可以被另外一个事务使用。
如果用户ROOLBACK,则服务器进程会根据数据文件块和DB BUFFER中块的头部的事务列表和SCN以及回滚段地址重构出相应的修改前的副本,并且用这些原值来还原当前数据文件中已修改但未提交的改变。如果有多个"前映像",服务器进程会在一个"前映像"的头部找到"前前映像"的回滚段地址,一直重构出同一事务下的最早的一个"前映像"为止。一旦发出了COMMIT,用户就不能ROLLBACK,这使得COMMITDBWN进程还没有全部完成的后续动作得到了保障。
下面我们要提到检查点的作用,ckpt的触发,有以下几种情况
1.当发生日志组切换的时候
2.当满足log_checkpoint_timeoutlog_checkpoint_intervalfast_start_io_targetfast_start_mttr_target参数设置的时候
3.当运行alter system switchlogfile的时候
4.当运行alter systemckeckpoint的时候
5.当运行altertablespacetbs_namebegin backup[end backup]的时候
6.当运行altertablespace[datafile] offline的时候
7.系统正常关闭时
只有在4.7两种情况下发生完全检查点。发生完全检查点时,首先系统记录检查点对应的Checkpoint SCN,并记录下该时刻修改的DB BUFFER对应的日志文件的最新的重做字节地址(Redo Byte Address (RBA)),然后DBWN进程将这个重做字节地址(RBA)之前已发生的DB BUFFER中的脏缓冲写入数据文件(之所以要以重做字节地址(RBA)为标志是因为在检查点发生到检查点完成之间的时间内,系统还在一直不断的产生修改,这些修改所产生的DB BUFFER脏缓冲,以及日志条目将不会影响这次检查点最后确认的一致性结果,也就是最后确认这个Checkpoint SCN之前的系统是一致的)。最后把Checkpoint SCNRBA更新至控制文件,Checkpoint SCN更新至每个数据文件头部,表明当前数据库是一致的。日志切换并不导致一个完全检点的发生,比如有三个日志文件组,当发生日志切换时发生检查点,而发生日志切换一般是因为当前的LGWR正在写重做日志,也就是LGWR当刚写满2号日志就立即触发检查点,于是系统开始核对3号日志中记录的REDO项目所对应的数据是否已经从DB BUFFER中写入数据文件(不管事务是否已提交),如果没有写入,检查点就触发DBWN进程将这些缓冲块写入数据文件,显然LGWR因此而发生等待,除此以外,检查点还让DBWN进程将在2号日志中对应修改的DB BUFFER块写入数据文件,然后继续LGWR进程,直到LGWR进程将LGWR触发之前存在于REDO LOG BUFFER中的所有缓冲(包含未提交的重做信息)写入重做日志文件,检查点再更新数据文件,控制文件头部SCN。其实LGWR等待的并不是CKPT的完成,而是等待CKPT触发的DBWN进程的完成。可以想像断电时可能既有未COMMIT的事务,也可能同时存在已COMMITDBWN未完成的情况,如果断电时有一个已COMMITDBWN动作没有完成的情况存在,因为已经COMMITCOMMIT会触发LGWR进程,所以不管DBWN动作是否已完成,该语句将要影响的行及其产生的结果一定已经记录在重做日志文件中了,则实例重启后,SMON进程从控制文件中记录的上一次重做字节地址(RBA)开始,按照重做日志文件中的条目对数据文件和回滚段重新做一遍即前滚,注意这些条目的操作在断电之前有的已经被DBWN写入了数据文件,有的还没有来得及写,不管有没有写进数据文件,前滚时都会再重新写一次(9I之前是这样的),9I之后,由于也在日志中记录了DBWN改写的块信息,系统会过滤掉已写入的条目而只重做那些未写入的条目。对于一个未提交事务,分几种情况来描述:1LGWRDBWN一致的情况即一个语句执行完成后很长时间也没有COMMIT,这种情况一般不存在DBWN来不及完成的情况。只是没有COMMIT而已。那么SMON将在前滚完成后,利用回滚段重构出具有最小SCN的前映像,并把它的值写回原位。2)事务执行中断电,即可能存在LGWRDBWN不同步的情况(因为DBWN之前会触发LGWR所以DBWN对数据文件的修改一定会被先记录在重做日志文件中。因此只可能存在已写入重做日志而未来得及写入数据文件的情况存在。而不可能存在已写入数据文件却没有写入日志文件的情况。),这种情况下SMON也会先前滚一点(即把数据文件与相应的日志文件先同步再回滚,之所以说前滚一点,是指仅LGWRDBWN之间进度的差距,而不是把这条语句进行到底再回滚,因为日志文件中记录的是执行语句操作的一个个块的修改信息,而不只是记录一条执行语句的字面内容),然后利用回滚段重构出具有最小SCN的前映像,并把它的值写回原位。由此可见,实例失败后用于恢复的时间由两个检查点之间的间隔大小来决定,我们可以通个四个参数设置检查点执行的频率,LOG_CHECKPOINT_IMTERVAL决定了两个检查点之间写入重做日志文件的系统物理块的大小,LOG_CHECKPOINT_TIMEOUT决定了两个检查点之间的时间长度,FAST_START_IO_TARGET决定了用于恢复时需要处理的块的大小,FAST_START_MTTR_TARGET直接决定了用于恢复的时间的长短。检查点的作用就是不断的确认LGWRDBWN之间的同步情况,以便实例失败后从上一个检查点开始恢复,问题是两个检查点之间LGWRDBWN大部分的操作是同步的,只是一小部分没有同步,这种传统的检查点使实例恢复做了比较多的无用功,因此,ORACLE引入了增量检查点,增量检查点会在上一次传统检查点发生后到下一次传统检查点发生之前,不断的更新记录在控制文件中重做字节地址(RBA)(CKPT进程每三秒更新一次,见下面DBWN讲述),这样实例失败后将直接从控制文件中记录的最后更新的重做字节地址(RBA)开始进行前滚和回滚,这就省略掉了恢复时大部份的重做日志的重做(即使在9I以后的版本里也省略掉了大部分的过滤重做日志条目的时间)。(对以上描述做一个简单的比喻:比如一个贸易公司下设经营部、货运部、监督部,经营部负责贸易合同的签订与记录,货运部负责按合同号的顺序把货物送达,监督部负责定期检查确认经营部签订的合同与货运部货物送达情况之间的同步情况,监督部每月检查一次,每次检查时,先确认当时正在装车的货物的合同号,并要求货运部把在这个合同号之前的所有还存在临时仓库中的未送货物全部送达。等货运部完成监督部下达的任务后,监督部在检查本上记录下本次开始检查时那票正在装车的货物的合同号,本次检查完成。如果这个公司发生了一次人事大换血,公司重新开业后,监督部就会从检查本上记录的合同号开始,检查在这之后所有发生的合同及货物送达情况,要求货运部把所有客户确认的但还未送达的货物送达。把所有客户未确认的货物收回。监督部发现这次重新开业后的工作量实在是太大了,几乎核对了整整一个月的几万单合同(好在不是半年检查一次^_^),为了防止今后出现这种情况,监督部增加了一项工作内容,每三天派人去记录一下货运部正在装车的那票货的合同号,今后如果发生类似情况,监督部就从最后一次记录的合同号开始核对,这样工作量就小多了。)
ORACLE的工作机制-4
肖亚峰(xyf_tck)
 
在这里我们要说一下回滚段存储的数据,假如是 delete 操作,则回滚段将会记录整个行的数据,假如是 update, 则回滚段只记录行被修改了的字段的变化前的数据(前映像),也就是没有被修改的字段是不会被记录的,假如是 insert ,则回滚段只记录插入记录的 rowid 。这样假如事务提交,那回滚段中简单标记该事务已经提交;假如是回退,则如果操作是 delete, 回退的时候把回滚段中数据重新写回数据块,操作如果是 update ,则把变化前数据修改回去,操作如果是 insert ,则根据记录的 rowid 把该记录删除。注意,检查点除了触发 LGWR DBWN 向数据块头部写 SCN COMMIT SCN ,检查点还向控制文件和数据文件头部写 SCN ,而用户的 DML COMMIT 仅是向数据块头部写 SCN COMMIT SCN 而不更新控制文件和数据文件的 SCN SMON 的前滚是 以文件头部的 SCN 为起始点的也就是从前一个检查点开始, SMON 的回滚是回滚所有回滚段中未标识为已提交的数据块,用户的回滚是回滚与此事务有关的回滚段中未标识为已提交的数据块。
下面我们要讲 DBWN 如何来写数据文件,在写数据文件前首先要找到可写的空闲数据块,
ORACLE 中空闲数据块可以通过 FREELIST BITMAP 来维护,它们位于一个段的头部用来标识当前段中哪些数据块可以进行 INSERT 。在本地管理表空间中 ORACLE 自动管理分配给段的区的大小,只在本地管理的表空间中才能选用段自动管理,采用自动段空间管理的本地管理表空间中的段中的空闲数据块的信息就存放在段中某些区的头部,使用位图来管理( 最普通的情况是一个段的第一个区的第一个块为FIRST LEVEL BITMAP BLOCK,第二个块为SECOND LEVEL BITMAP BLOCK,第三个块为PAGETABLE SEGMENT HEADER,再下面的块为记录数据的数据块,FIRST LEVEL BITMAP BLOCK的父数据块地址指向SECOND LEVEL BITMAP BLOCK,SECOND LEVEL BITMAP BLOCK的父数据块地址指向PAGETABLE SEGMENT HEADER,FIRST LEVEL BITMAP BLOCK记录了它所管理的所有块(包括头部三个块,不仅仅指数据块)的状态,标识的状态有Metadata、75-100% free、50-75% free、25-50% free、0-25% free、full、unformatted,在SECOND LEVEL BITMAP BLOCK中有一个列表,记录了它管理的FIRST LEVEL BITMAP BLOCK,PAGETABLE SEGMENT HEADER中记录的内容比较多,除了记录了它管理的SECOND LEVEL BITMAP BLOCK,还记录了各个区的首块地址以及各个区的DB BLOCK的个数,段的各个区所对应的FIRST LEVEL BITMAP BLOCK的块地址以及区里面记录数据的数据块的起始地址。如果一个区拥有很多块,这时会在一个区里出现两个或多个FIRST LEVEL BITMAP BLOCK,这些FIRST LEVEL BITMAP BLOCK分别管理一个区中的一些块,当区的数据块比较少时,一个区的FIRST LEVEL BITMAP BLOCK可以跨区管理多个区的数据块,BITMAP BOLCK最多为三级 )。采用手动管理的本地管理表空间中的段和数据字典管理的表空间中的段中的空闲数据块的管理都使用位于段头部的空闲列表来管理,例如 SYSTEM 表空间 是本地管理表空间,但是它是采用了手动段空间管理,所以也是用 FREELIST 来管理段中的空闲数据块的。空闲列表是一个逻辑上的链表,在段的 HEADER BLOCK 中记录了一个指向第一个空闲块的 BLOCK ADDRESS ,第一个 DB BLOCK 中同时也记录了指向下一个空闲块的 BLOCK ADDRESS 。以此形成一个单向链表。如果段上有两个 FREE LIST 则会在段头部的 HEADER BLOCK 存有两个指针分别指向两个空闲块并建立独立的两个单向链表。空闲列表的工作方式:首先当建立一个段时,初始分配的第一个区的第一个块会成为段的头块,初始分配的第一个区的其它块将全部加入空闲列表,再次扩展一个区时,这个区中的块立即全部加入空闲列表,扩展一次加入一次。与位图管理不同的是用空闲列表时区的头部将不记录区里面空闲块的信息。当其中空闲空间小于 PCTFREE 设置的值之后,这个块从空闲列表删除,即上一个指向它的块中记录的下一个空闲块地址更改为其它空闲块的地址,使得这个块类似于被短路,当这个块中的内容降至 PCTUSED 设置的值之下后,这个数据块被再次加入空闲列表,而且是加入到空闲列表前端,即头块直接指向它,它再指向原头块指向的空闲块,位于空闲列表中的数据块都是可以向其中 INSERT 的块,但是 INSERT 都是从空闲列表指向的第一个块开始插入,当一个块移出了空闲列表,但只要其中还有保留空间就可以进行 UPDATE ,当对其中一行 UPDATE 一个大数据时,如果当前块不能完全放下整个行,只会把整个行迁移到一个新的数据块,并在原块位置留下一个指向新块的指针,这叫行迁移。如果一个数据块可以 INSERT ,当插入一个当前块装不下的行时,这个行会溢出到两个或两个几上的块中,这叫行链接。如果用户的动作是 INSERT 则服务器进程会先锁定 FREELIST ,然后找到第一个空闲块的地址,再释放 FREELIST ,当多个服务器进程同时想要锁定 FREELIST 时即发生 FREELIST 的争用,也就是说多个进程只在同时 INSERT 时才会发生 FREELIST 争用,可以在非采用自动段空间管理的表空间中创建表时指定 FREELIST 的个数,默认为 1 ,如果是在采用自动段空间管理的表空间中创建表,即使指定了 FREELIST 也会被忽略,因为此时将使用 BITMAP 而不是 FREELIST 来管理段中的空闲空间。采用自动段空间管理还会忽略的参数有 PCTUSED FREELIST GROUPS 。如果用户动作是 UPDATE DELETE 等其它操作,服务器进程将不会使用到 FREELIST BITMAP ,因为不要去寻找一个空闲块,而使用锁的队列。对数据块中数据操作必须使用 transaction entries ,即事务入口。在建立段时我们可以通过 MINTRANS MAXTRANS 参数指定它的最大值和最小值, MAXTRANS 规定了在段中每一个块上最大并发事务数量,可以输入 1 255 之间的值。我们可以把它比喻为是一些长在块头部的事务插座,每个插座后面是一个可以伸缩的操作手,当事务进程插到一个插座上时相当于找到一个可以操作数据块中数据行的操作手,通过这个操作手,事务进程可以对块中数据进行 INSERT UPDATE DELETE 等操作。在没有超过 MAXTRANS 设定的最大值时,如果 transaction entries 不够用,则会在块上自动分配一个,但不会影响其它块中的 transaction entries 数量。只不过 INSERT 操作必须要先找到空闲块然后才能 INSERT
那么 DBWN 是根据什么顺序来写 DB BUFFER 中的脏数据的呢? ORACLE 8I 开始加入新的数据结构 -- 检查点队列 (Buffer Checkpoint Queue) 。检查点队列是一个链接队列。这个队列的按照 Buffer 块第一次被修改的顺序排列,分别指向被修改的 Buffer 块。在 DB_Buffer 中的数据被第一次被修改时,会记录所生成的 REDO LOG 条目的位置 RBA 作为该 Buffer Low RBA ,记录在该 Buffer 的头部( Buffer Header ),如果该数据继续被修改,则把该块修改的最新的 REDO LOG RBA 作为 High RBA 记录在该 Buffer 的头部。如果 DB_Buffer 中的块没有被修改的数据,则该块的头部不会有 Low RBA High RBA 的信息。检查点队列按照被修改块的 Low RBA 的递增值链接修改块,没有被修改的块因为没有 Low RBA ,而不会加入到检查点队列中。在没有检查点发生时 DBWR 就按照检查点队列的 Low RBA 的升序,将被修改的块写入到数据文件中。当块被写入到数据文件后,该块会从检查点队列中断开。 DBWR 继续写下一个块。 CKPT 进程每三秒记录检查点队列中对应的最小 Low RBA 到控制文件中,也就是更新控制文件中的 CheckPoint RBA ,当实例崩溃时,恢复将从 CheckPoint RBA 所指向的日志位置开始。这就是 " 增量检查点 " 的行为和定义。 CKPT 进程也会记录检查点位置到数据文件的头部,但是只是日志切换时才写。而不是每三秒。当检查点发生时, DBWN 不会一直不停的写 DB BUFFER 中脏数据 ,它将写到检查点队列的开始块的 Low RBA 的值大于该检查点的 Checkpoint RBA 的值时停止写入,然后完成这次检查点, CKPT 进程将记录该检查点的有关信息到控制文件中去。    
ORACLE的工作机制-5
肖亚峰(xyf_tck)
 
下面来讲一下 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 参数决定了表中的一个数据块同时最多可以被几个事务锁定。
下面来讲一下 ORACLE 的读一致性机制, ORACLE 的读一致性保证了事务之间的高度隔离性。
下面是几个关于回滚段读一致性和死锁的事例:
有表: Test (id number(10))  有记录 1000000
SQL> create table test (idnumber(10))tablespaceusers;
表已创建。
SQL> begin
 2 foriin 1..1000000 loop
 3 insertinto test values(i);
 4 endloop;
 5 commit;
 6 end;
 7 /
PL/SQL 过程已成功完成
,大 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 大,因此也会利用回滚段进行重构。根据当前块上所有的 itl 找到相应的 undo 地址,重构出之前的 block image, 之前的那个 block 又含有自己的 itl 信息 . 如果这个 before image 中对应的 scn 不满足查询 , 又会根据 undo 生成 beforebefore image, 这样不断往复 , 直到构造出符合查询的 scn block 返回结果 , 或者系统实在无法根据 undo 构造出符合查询的 block,, ora-01555 的错误为止 .... 。需要强调的是读一致性是通过对当前整个块利用回滚段(当前块上的所有 ITL 记录的所有回滚段地址)进行递归重构到过去某一时间点或某一 SCN 的块的一致性快照。而不是只针对块中一部分 ITL 记录在回滚段中递归查找来完成的,一定要理解递归重构与递归查找是完全不同的两个概念。 Oracle 回滚段确保 了事务的高度的隔离性。即只要回滚段足够大,那么一个 SELECT 不管执行多长,它读取的所有数据都将是在这条 SELECT 语句开始执行瞬间这个时间点的值,而不会被其它用户在 SELECT 读取期间对数据是否做过修改而影响。
二、大 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值