log buffer及日志管理深入分析及性能调整(二)

来源:互联网 发布:文本框空js弹出遮罩层 编辑:程序博客网 时间:2024/06/01 10:21

接上文:log buffer及日志管理深入分析及性能调整(一)

日志缓冲区的内部管理分为两部分,一部分是重做记录的生成,另一部分就是重做记录写入联机日志文件。这两部分不是孤立的,没有关联的。在生成重做记录的过程中,可能会触发LGWR将重做记录写入联机日志文件。

      我们先用一个实例来说明在日志缓冲区中的操作过程,并使用[file# , blk#]来表示某个数据块,file#表示文件号,blk#表示数据块号。

假设session 1发出更新语句:update redo_test set name='cdf' where id=1;

Oracle首先找出id=1所在的数据块(假设为[file#4,blk#120])放入buffercache,然后找出一个可用的回滚段数据块(假设为[file#2,blk#19]),将旧值'abc'放入该块,同时生成重做记录。然后将'cdf'放入表的数据块,再生成重做记录。这时的日志缓冲区的结构类似如下。(我们从前面描述日志缓冲区的内存结构时,知道重做记录中最重要的就下面列的这几列内容。同时,下面的一行就表示一个改动向量):

行号 事务id     file#  block# row    column     value

1    T1         2      19     -      -          abc

2      T1         4      120    1      2          cdf

         这时假设session 2发出其他更新语句:update t set c1=10 where c1=9;

同样的道理,oracle找到该数据块(假设为[file#5,blk#200])放入buffercache,并找到回滚段数据块(假设为[file#2,blk#30])存放旧值,生成重做记录,更新表的数据块,再次生成重做记录。这时的日志缓冲区的结构类似如下:

行号   事务id     file#  block# row    column     value

1      T1         2      19     -      -          abc

2      T1         4      120    1      2          cdf

3      T20        2      30     -      -          9

4      T20        5      200    20     1          10

这时,session 1又发出更新语句:updateredo_test set name='xyz1' where id=2,并提交(commit)。

同样的方式处理回滚段和数据块,并生成重做记录。假设这时生成日志缓冲区假设为:

行号   事务id     file#  block# row    column     value

1      T1         2      19     -      -          abc

2      T1         4      120    1      2          cdf

3      T20        2      30     -      -          9

4      T20        5      200    20     1          10

5      T1         2      19     -      -          abc

6      T1         4      120    2      2          xyz1

7      T1         commit SCN    timestamp

这时,我们可以注意到,提交标记也被记录到了重做记录中。从我们前面转储出的结果中也可以看到“sta: 9”的内容,这就是提交标记。每次提交时,都会生成一个SCN号,SCN号越小,说明发生的越早,其所属的重做记录就越排在前面。一旦用户发出commit语句,系统就会触发LGWR进程。这时,LGWR进程会将上面所显示的所有重做记录都写入联机日志文件中。注意,其中也包括尚未提交的事务T20。

在LGWR写这些重做记录的过程中,又有其他session发出更新语句,并提交。这时的日志缓冲区假设如下所示:

行号   事务id     file#  block# row    column     value

1      T1         2      19     -      -          abc

2      T1         4      120    1      2          cdf

3      T20        2      30     -      -          9

4      T20        5      200    20     1          10

5      T1         2      19     -      -          abc

6      T1         4      120    2      2          xyz1

7      T1         commit SCN    timestamp                以上的重做日志正在由LGWR写入

------------------------------------------------------------------------------------------

8      T20        2      39     -      -          289  在LGWR写时生成以下的重做日志

9      T20        5      498    220    3          190

10     T9         2      90     -      -          hhh

11     T9         9      100    20     9          xxx

12     T9         commitSCN     timestamp

13     T18        2      189    -      -          18

14     T18        10     29     300    10         20

15     T18        commitSCN     timestamp

当LGWR写完第一批重做记录(第1到第7行)以后,就会立即开始写第二批重做记录(第8行到第15行)。注意,第二批重做记录中,存在两个commit,但LGWR不会分成两次来写,而是一次就将它们全部写入。当LGWR在写完第1到第7行的改动向量以后,这部分的日志缓冲区内存就被释放了,可以被新生成的重做记录所覆盖。

如果当前联机日志文件写完时,这时就需要转换到另外一个可写的联机日志文件上去,这个过程叫做日志切换。日志切换的大致过程包括:

从控制文件中得到下一个可用的联机日志文件。

记录写入当前联机日志文件的最后一个日志块的SCN(叫做high SCN)。关闭当前联机日志文件。

增加SCN,再次操作控制文件,将下一个联机日志文件标志为CURRENT,判断前一个联机日志文件里包含的重做记录所对应的脏数据块是否都已经写入数据文件,如果没有则标记为ACTIVE;如果是则标记为INACTIVE。如果数据库是归档模式,那么LGWR将前一个联机日志文件加入归档列表中,等待归档。

打开新的联机日志文件组中的所有成员,记录当前日志序列号(log sequence)和第一个日志块的SCN(叫做low SCN),新一轮的重做记录开始。

我们经常看到当前日志文件的状态为CURRENT,而前一个日志文件的状态为ACTIVE的情况。实际上,这是由于内存中存在很多脏数据块,而脏数据块的写入是通过DBWR进程完成的。如果脏数据块没有积累到一定的量,DBWR是不会将它们写入数据文件的。所以,ACTIVE状态的日志文件表示该日志文件里包含的重做记录所对应的脏数据块还没有被DBWR进程写入数据文件。事实上,日志切换时触发增量检查点(incremental checkpoint),CKPT将会触发DBWR写脏数据块。增量检查点只是在控制文件中记录这时在检查点队列上的脏数据块在第一次修改时所对应的日志块在日志文件中的地址,这个地址叫做检查点位置(checkpoint position)。但是DBWR启动并不表示立即写脏数据块,除非脏数据块的数量达到一定程度,或超过一定时间等。我们来模拟一下这种情况。

SQL> select group#,status from v$log;

   GROUP# STATUS

---------- ----------------

        1CURRENT

        2INACTIVE

        3INACTIVE

SQL> create table t2 as select * fromdba_objects;

SQL> select group#,status from v$log;

   GROUP# STATUS

---------- ----------------

        1ACTIVE

        2CURRENT

        3INACTIVE

我们可以看到,当创建了表t2的时候,发生了日志切换。如果没有看到日志切换,可以再删除表t2然后再创建表t2,如此反复几次,就能够引起日志切换。这时我们看到2号日志文件为当前打开的日志文件,其状态为CURRENT。而1号日志文件为上次打开的日志文件,状态为ACTIVE,就说明其对应的脏数据块还没有写入数据文件。这时我们可以等待一段时间,等待增量检查点的完成,也可以手工触发完全检查点(complete checkpoint),触发完全检查点时,DBWR将把所有脏数据块写入磁盘上的数据文件里。

SQL> alter system checkpoint;

SQL> select group#,status from v$log;

   GROUP# STATUS

---------- ----------------

        1INACTIVE

        2CURRENT

        3INACTIVE

可以看到,1号日志文件的状态从ACTIVE变成了INACTIVE,说明其对应的脏数据块都已经写入了联机日志文件中。

这里要说明一点。增量检查点触发的DBWR写和完全检查点触发的DBWR写的优先级是不一样的,这也就是为何在上面的例子中进行日志切换以后,前一个日志的状态为ACTIVE,而且要等一段时间以后其状态才能变为INACTIVE,而我们发出alte system checkpoint以后立即变为INACTIVE的原因。因为日志切换触发增量检查点,而增量检查点通知DBWR启动,但是由于这时触发DBWR启动的条件的优先级较低,所以DBWR不会立即去写脏数据块,而是要等一段时间才会实际的写脏数据块。所以我们等待日志状态变为INACTIVE的时间就是等DBWR开始真正写的时间加上DBWR实际写入数据文件所花费的时间。而alte system checkpoint触发完全检查点,其优先级很高,所以通过它触发的DBWR会立即去写脏数据块,所以我们等待日志状态变为INACTIVE的时间就是DBWR实际写入数据文件所花费的时间。

我们可以设定初始化参数:log_checkpoints_to_alert为true,从而将检查点启动和结束的时间记录到跟踪文件里去。这里是所记录的信息的一个例子:

Wed Dec 13 18:27:48 2006

Beginning log switch checkpoint up to RBA[0x85.2.10], SCN: 2164686

Thread 1 advanced to log sequence 133

 Current log# 3 seq# 133 mem# 0: /oracle/oradata/ora10g/redo03.log

Wed Dec 13 18:32:45 2006

Completed checkpoint up to RBA [0x85.2.10],SCN: 2164686

……

Wed Dec 13 19:02:15 2006

Beginning global checkpoint up to RBA[0x85.883.10], SCN: 2165818

Completed checkpoint up to RBA[0x85.883.10], SCN: 2165818

从上面可以看到,由于日志切换而发生的增量检查点从18:27:48开始,到18:32:45结束,用了5分钟的时间。而我们强制进行完全检查点,则只用了大概1秒钟不到的时间。实际上,DBWR在实际写入数据文件所花费的时间都是一样的,也就是不到1秒。5分钟的差别就在于DBWR等了5分钟才实际开始写数据文件。

在大致了解了日志缓冲区的操作过程以后,我们来深入了解一下其内部的工作原理是怎样的。

当用户发出DML语句,请求更新某些表里的数据时,在日志缓冲区中生成重做记录的过程如下所示。

服务器进程判断buffer cache中是否存在要求被更新的数据块,如果没有,则从数据文件中将数据块调入buffer cache。然后以排他(Exclusive)模式将该数据块钉住(ping)。注意,这里的数据块包括回滚段段头(事务表)、回滚段数据块以及表所对应的数据块等。

服务器进程构造一组改动向量(changevector)来描述对数据块所做的变化过程。这组改动向量会放在session的PGA中。实际上,这组改动向量也就是重做记录了。

根据重做记录的大小,判断在日志缓冲区中需要多少空间。

判断当前的SCN值,并将其存放到重做记录中。注意,并不是每个重做记录都具有不同的SCN值,不同的重做记录可能会共享相同的SCN值。

 进程获得名为redocopy的latch。

进程获得名为redo allocation的latch。

检查当前是否有其他进程生成了比当前持有的SCN更高的SCN值。如果是,则生成一个新的SCN值,并代替第四步所持有的SCN值。

 判断是否有足够的空间容纳当前的重做记录,这里足够的空间既包括日志缓冲区,也包括联机日志文件。这时的逻辑比较复杂。

如果联机日志文件中有足够的空间,则判断日志缓冲区是否有足够的空间。

 I.     如果日志缓冲区中没有足够的空间,则进程释放redo copy latch和redo allocation latch。然后在下面的两个选项中做一个选择:(1)如果这时已经有其他进程或其他条件触发了LGWR,则等待LGWR的完成;(2)如果这时LGWR没有启动,则触发LGWR启动。为了防止多个进程同时触发LGWR,oracle还引入了名为redowriting的latch。当LGWR进程在写重做记录到联机日志文件的过程中,会一直持有redo writing latch,这时任何试图获得该latch的进程都必须等待。实际上,当进程发现没有足够的空间以后,会立即尝试获取redo writing latch。如果不能获得该latch则说明LGWR正在清空日志缓冲区,所以进程会等待,过一段时间再次尝试去获得该latch;如果获得了该latch,则立即检查这时日志缓冲区中是否还有足够的空间,如果有,则释放redo writing latch,并再次尝试获得redo copy latch和redo allocation latch;如果没有,则释放redo writing latch,同时触发LGWR,这时LGWR获得redo writing latch。

II.     如果日志缓冲区中有足够的空间,则从日志缓冲区中分配所需大小的空间,然后释放redo allocation latch,注意这时仍持有redo copy latch。将改动向量拷贝到所分配的日志缓冲区中,将重做记录所对应的脏数据块挂到检查点队列(checkpoint queue)上去。最后,释放redo copy latch。

如果联机日志没有足够的空间(可能有一部分可用空间,但是不够用。实际这就是有时你会看到归档日志文件的尺寸会小于联机日志文件的尺寸的原因),则进程会检查是否已经有进程已经触发日志切换了,如果有,则当前进程等待,如果没有则触发日志切换。然后,重复第8步。

当在日志缓冲区中写完重做记录以后,检查当前日志缓冲区中的重做记录的数量是否达到限定值,如果是,则必须触发LGWR进程。

 最后,进程更新buffer cache中的数据块。

我们可以用一个图来描述一下上面所说的日志缓冲区的管理过程,如下图四所示。

图四

         上面详细介绍了重做记录的生成过程,现在详细介绍一下LGWR写重做记录的过程。

当LGWR进程启动时,首先会尝试获取redowriting latch,以确保其他前台进程不会继续触发LGWR进程。然后会获取redo allocation latch,这是为了防止前提进程继续分配更多的日志缓冲区,否则日志缓冲区中的待写入日志文件的日志块不断增长,LGWR是无法确定到底应该写多少日志块的。

          在获得了redo allocation latch以后,LGWR开始确定应该写哪些日志块到日志文件。从上次LGWR启动以来所写的最后一个日志块到这个时间点时的最后一个被使用的日志块,这段范围内的日志块都是要被写入日志文件的,其中既包含写满的日志块,也可能包含还没有写满的日志块。特别注意,这个时候,可能还有前台进程正在向这段范围中的日志块拷贝重做记录。因为LGWR启动时,并不会阻碍前台进程获得redo copy latch,也就不会阻碍前台进程拷贝重做记录了。这样的话,LGWR就不会阻碍前台进程向日志缓冲区的其他可用的日志块中拷贝重做记录了。

 在确定了要写哪些日志块以后,生成一个新的SCN号。

LGWR释放redoallocation latch,并释放redo writing latch。

LGWR会等待前台进程完成对LGWR所要写入文件的日志块的更新操作。这是通过判断这些待写的日志块上的redo copy latch是否都被释放来决定的。

 LGWR将第4步生成的SCN号拷贝进待写的日志块的块头里,触发物理写操作,将这些待写日志块写入联机日志文件里去。


0 0