本文我们主要介绍了Oracle服务进程如何处理用户进程的请求所完成的7个任务:sql语句的解析、数据块的读入db buffe、记日志、为事务建立回滚段、本事务修改数据块、放入dirty list以及用户commit或rollback。希望能够对您有所帮助。 Oracle服务器进程在完成用户进程的请求过程中,主要完成如下7个任务:0.sql语句的解析;1.数据块的读入db buffer(写入数据缓存);2.记日志;3.为事务建立回滚段;4.本事务修改数据块;5.放入dirty list;6.用户commit或rollback。接下来我们就分别来介绍一下这7个任务的相关知识,希望能够对您有所帮助。2 _: T/ B7 z4 x9 o, K
0.sql语句的解析
/ z5 X$ p; f* n2 i 下面要讲oracle服务器进程如可处理用户进程的请求,当一用户进程提交一个sql时:update temp set a=a*2;首先oracle服务器进程从用户进程把信息接收到后,在PGA中就要此进程分配所需内存,存储相关的信息,如在会话内存存储相关的登录信息等;
1 Z g, p: f2 n 服务器进程把这个sql语句的字符转化为ASCII等效数字码,接着这个ASCII码被传递给一个HASH函数,并返回一个hash值,然后服务器进程将到shared pool中的library cache中去查找是否存在相同的hash值,如果存在,服务器进程将使用这条语句已高速缓存在SHARED POOL的library cache中的已分析过的版本来执行,如果不存在,服务器进程将在CGA中,配合UGA内容对sql,进行语法分析,首先检查语法的正确性,接着对语句中涉及的表,索引,视图等对象进行解析,并对照数据字典检查这些对象的名称以及相关结构,并根据ORACLE选用的优化模式以及数据字典中是否存在相应对象的统计数据和是否使用了存储大纲来生成一个执行计划或从存储大纲中选用一个执行计划,然后再用数据字典核对此用户对相应对象的执行权限,最后生成一个编译代码。
3 A9 x" g5 [' p: [' X$ l- r( h ORACLE将这条sql语句的本身实际文本、HASH值、编译代码、与此语名相关联的任何统计数据和该语句的执行计划缓存在SHARED POOL的library cache中。服务器进程通过SHARED POOL 锁存器(shared pool latch)来申请可以向哪些共享PL/SQL区中缓存这此内容,也就是说被SHARED POOL锁存器锁定的PL/SQL区中的块不可被覆盖,因为这些块可能被其它进程所使用。在SQL分析阶段将用到LIBRARY CACHE,从数据字典中核对表、视图等结构的时候,需要将数据字典从磁盘读入LIBRARY CACHE,因此,在读入之前也要使用LIBRARY CACHE锁存器(library cache pin,library cache lock)来申请用于缓存数据字典。
5 s. t6 ]$ e b- \ 到现在为止,这个sql语句已经被编译成可执行的代码了,但还不知道要操作哪些数据,所以服务器进程还要为这个sql准备预处理数据。( M2 Q6 v( w8 L
1.数据块的读入db buffer
" k2 q+ a' q) o3 ]- o% a Oracle处理数据,都需要把数据读取到内存中(即db buffer中),首先服务器进程要判断所需数据是否在db buffer存在,如果存在且可用,则直接获取该数据,同时根据LRU算法增加其访问计数;如果buffer不存在所需数据,则要从数据文件上读取。首先服务器进程将在表头部请求TM锁(保证此事务执行过程其他用户不能修改表的结构),如果成功加TM锁,再请求一些行级锁(TX锁),如果TM、TX锁都成功加锁,那么才开始从数据文件读数据,在读数据之前,要先为读取的文件准备好buffer空间。服务器进程需要扫面LRU list寻找free db buffer,扫描的过程中,服务器进程会把发现的所有已经被修改过的db buffer注册到dirty list中,. u* y0 c$ V/ r0 y" g: U' B
这些dirty buffer会通过dbwr的触发条件,随后会被写出到数据文件,找到了足够的空闲buffer,就可以把请求的数据行所在的数据块放入到db buffer的空闲区域或者覆盖已经被挤出LRU list的非脏数据块缓冲区,并排列在LRU list的头部,也就是在数据块放入DB BUFFER之前也是要先申请db buffer中的锁存器,成功加锁后,才能读数据到db buffer。
& Z; D* ^" j) n. }' D8 h 2.记日志& {; H: N/ B& A ~. a* ` A
现在数据已经被读入到db buffer了,现在服务器进程将该语句所影响的并被读入db buffer中的这些行数据的rowid及要更新的原值和新值及scn等信息从PGA逐条的写入redo log buffer中。在写入redo log buffer之前也要事先请求redo log buffer的锁存器,成功加锁后才开始写入,当写入达到redo log buffer大小的三分之一或写入量达到1M或超过三秒后或发生检查点时或者dbwr之前发生,都会触发lgwr进程把redo log buffer的数据写入磁盘上的redo file文件中(这个时候会产生log file sync等待事件),已经被写入redo file的redo log buffer所持有的锁存器会被释放,并可被后来的写入信息覆盖,redo log buffer是循环使用的。Redo file也是循环使用的,当一个redo file 写满后,lgwr进程会自动切换到下一redo file(这个时候可能出现log file switch(checkpoint complete)等待事件)。如果是归档模式,归档进程还要将前一个写满的redo file文件的内容写到归档日志文件中(这个时候可能出现log file switch(archiving needed))。
8 L C l$ J, ~- f4 l: { 3.为事务建立回滚段. c4 O- U% N. P; L! q
在完成本事务所有相关的redo log buffer之后,服务器进程开始改写这个db buffer的块头部事务列表并写入scn,然后copy包含这个块的头部事务列表及scn信息的数据副本放入回滚段中,将这时回滚段中的信息称为数据块的“前映像“,这个”前映像“用于以后的回滚、恢复和一致性读。(回滚段可以存储在专门的回滚表空间中,这个表空间由一个或多个物理文件组成,并专用于回滚表空间,回滚段也可在其它表空间中的数据文件中开辟。)
- z# a- g7 j. a8 o8 K# w/ ? 4.本事务修改数据块
& s; m1 V4 j# \: G) k2 R6 H 准备工作都已经做好了,现在可以改写db buffer块的数据内容了,并在块的头部写入回滚段的地址。
3 T, {) _2 S" I2 d. Q" L 5. 放入dirty list$ r$ _' c2 C, s8 O/ ` b$ p) _
如果一个行数据多次update而未commit,则在回滚段中将会有多个“前映像“,除了第一个”前映像“含有scn信息外,其他每个“前映像“的头部都有scn信息和“前前映像”回滚段地址。一个update只对应一个scn,然后服务器进程将在dirty list中建立一条指向此db buffer块的指针(方便dbwr进程可以找到dirty list的db buffer数据块并写入数据文件中)。% v7 `( o: r$ A
接着服务器进程会从数据文件中继续读入第二个数据块,重复前一数据块的动作,数据块的读入、记日志、建立回滚段、修改数据块、放入dirty list。当dirty queue的长度达到阀值(一般是25%),服务器进程将通知dbwr把脏数据写出,就是释放db buffer上的锁存器,腾出更多的free db buffer。前面一直都是在说明oracle一次读一个数据块,其实oracle可以一次读入多个数据块(db_file_multiblock_read_count来设置一次读入块的个数)
J- p: [2 W! G+ u' o 说明:
6 M+ q, G( z w/ B& k+ @3 h 在预处理的数据已经缓存在db buffer或刚刚被从数据文件读入到db buffer中,就要根据sql语句的类型来决定接下来如何操作。' k( p3 r' K+ A4 i2 U+ p' J
1>如果是select语句,则要查看db buffer块的头部是否有事务,如果有事务,则从回滚段中读取数据;如果没有事务,则比较select的scn和db buffer块头部的scn,如果前者小于后者,仍然要从回滚段中读取数据;如果前者大于后者,说明这是一非脏缓存,可以直接读取这个db buffer块的中内容。
* y6 D0 X% {- t, v) h% O' ` 2>如果是DML操作,则即使在db buffer中找到一个没有事务,而且SCN比自己小的非脏缓存数据块,服务器进程仍然要到表的头部对这条记录申请加锁,加锁成功才能进行后续动作,如果不成功,则要等待前面的进程解锁后才能进行动作(这个时候阻塞是tx锁阻塞)。
3 y* g, E. B) d 6.用户commit或rollback0 T' R& \: Z1 |% A2 U* a5 Y
到现在为止,数据已经在db buffer或数据文件中修改完成,但是否要永久写到数文件中,要由用户来决定commit(保存更改到数据文件)和rollback(撤销数据的更改),下面来看看在commit和rollback时,oracle都在做什么。$ @" M% D$ ^' X/ Q5 N! M' a
用户执行commit命令
4 f: n# p! u' y 只有当sql语句所影响的所有行所在的最后一个块被读入db buffer并且重做信息被写入redo log buffer(仅指日志缓冲区,而不包括日志文件)之后,用户才可以发去commit命令,commit触发lgwr进程,但不强制立即dbwr来释放所有相应db buffer块的锁(也就是no-force-at-commit,即提交不强制写),也就是说有可能虽然已经commit了,但在随后的一段时间内dbwr还在写这条sql语句所涉及的数据块。表头部的行锁并不在commit之后立即释放,而是要等dbwr进程完成之后才释放,这就可能会出现一个用户请求另一用户已经commit的资源不成功的现象。. Z; T# h* a, T) K! ~
; V# q8 C' z! R7 S9 M8 [; v
A .从Commit和dbwr进程结束之间的时间很短,如果恰巧在commit之后,dbwr未结束之前断电,因为commit之后的数据已经属于数据文件的内容,但这部分文件没有完全写入到数据文件中。所以需要前滚。由于commit已经触发lgwr,这些所有未来得及写入数据文件的更改会在实例重启后,由smon进程根据重做日志文件来前滚,完成之前commit未完成的工作(即把更改写入数据文件)。 |