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

来源:互联网 发布:二维平移矩阵 编辑:程序博客网 时间:2024/05/22 09:03

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

上一篇 /下一篇  2008-07-30 22:16:01 / 个人分类:工作技术

查看( 645 ) /评论( 0 ) / 评分( 0 / 0 )

有关log buffer的深入分析以前也在it168发过,现在整理到自己的blog里。

<!--[if !supportLists]-->

1.    <!--[endif]-->log buffer的概念

1.1 log buffer概述

数据库在运行过程中,不可避免的要遇到各种能够导致数据块库损坏的情况。比如突然断电、oracle

或者操作系统的程序bug导致数据库内部逻辑结构损坏、磁盘介质损坏等,都有可能造成数据库崩溃,从而导致数据丢失的现象发生。

      为了避免,或者说为了修复这些状况所导致的数据丢失现象,oracle引入了日志缓冲区和日志文件的概念。所谓日志,就是将所有数据库中所有改变数据块的操作,都原原本本的记录下来。这些改变数据块的操作不仅包括对数据表的DML或者对数据字典的DDL,还包括对索引的改变、对回滚段数据块的改变、对临时表空间的临时段的改变等。只有将数据库中所有的变化都记录下来,当发生数据库损坏时,才能够从损坏时的那一点开始,将之后数据库中的变化重新运用一遍,从而达到恢复数据库的目的。

      既然是要记录,那就必然引出一个问题,就是如何记录这些变化?比较容易想到的有两种方式。

第一种是使用逻辑的记录方式,也就是用描述性的语句来记录整个变化过程。比如对于某个update更新操作来说来说,可以记录为两条语句:delete旧值以及insert新值。这种方式的优点是非常节省空间,因为对每个操作,只需要记录几条逻辑上的语句即可。但是缺点也很明显,就是一旦需要进行恢复,就会非常消耗资源。设想一下,某个update操作更新了非常多的数据块,由于buffer cache内存有限,很多脏数据块都已经写入了数据文件。但就在更新快结束时,突然发生断电,所做的更新丢失。那么重新启动实例时,oracle需要应用日志文件里的记录,于是重新发出delete旧值以及insert新值的语句。这个过程需要重新查找数据文件中符合条件的数据块,然后再挑出来进行更新。这个过程将非常消耗时间,而且会占用大量的buffer cache。

      第二种方式是使用物理的记录方式,也就是将每个数据块改变前的镜像和改变后的镜像都记录下来。这种方式优点就是恢复起来速度非常快,直接根据日志文件里所记录的数据块地址和内容更新数据文件中对应的数据块。但是缺点也很明显,就是非常占用磁盘空间。

      而oracle在记录日志的方式上,采用了逻辑和物理相结合的方式。也就是说,oracle针对每个数据块,记录了插入某个值或者删除某个值的描述语句。假如某个update更新了100个数据块,则oracle会针对每个数据块记录一对delete旧值和insert新值的语句,共有100对这样的描述语句。通过这种方式,oracle获得了物理记录方式的快速恢复的优点,同时又获得了逻辑记录方式的节省空间的优点。

      为了临时存放所产生的日志信息,oracle在SGA中开辟了一块内存区域。这块区域就叫做日志缓冲区(log buffer),当满足一定条件以后,oracle会使用名为LGWR的后台进程将log buffer中的日志信息写入联机日志文件里。

      可以使用初始化参数log_buffer来设置日志缓冲区的大小,单位是字节。日志缓冲区会进一步细分为多个块,每个块的尺寸与操作系统的一个块的尺寸相同,基本都是512字节。我们可以用如下方式来获得日志缓冲区的块尺寸。

SQL> select distinct lebsz as redo_block_size from x$kccle;

REDO_BLOCK_SIZE

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

           512

也可以用下面的方式来计算出日志缓冲区的块尺寸。

SQL> select round((a.redosize+b.redowast)/c.redoblks) + 16 as redo_block_size from

 2 (select value redosize from v$sysstat where name='redo size') a,

 3 (select value redowast from v$sysstat where name='redo wastage') b,

 4 (select value redoblks from v$sysstat where name='redo blocks written') c

 5 ;

REDO_BLOCK_SIZE

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

           512

日志缓冲区只是日志信息临时存放的区域,这块区域是有限的,而且其中的每个块都是能够循环使用的。这也就说明,日志缓冲区中的内容必须要写入磁盘上的文件里,才能永久保留下来,才能在数据库崩溃时能够用来进行恢复。这个文件就叫做联机日志文件。在每个日志缓冲区中的日志块被重用之前,其内容必然已经被写入了磁盘上的联机日志文件中。

联机日志文件就是日志缓冲区的完全拷贝,组成日志文件的每个日志块的内容都来自于日志缓冲区的日志块。每个日志缓冲区中的日志块都对应到日志文件中的一个日志块。日志缓冲区中的日志块按照发生的先后顺序,放入联机日志文件。由于日志文件在故障恢复中的重要性,建议至少使用两个日志文件组成一个日志文件组。同一个日志文件组中的日志文件内容一摸一样,因为日志缓冲区中的日志块同时会写入日志文件组中的每个日志文件中。每个数据库都必须至少拥有两个日志文件组。这是由于只要数据库一天不停止运行,就会不断产生日志信息,就会不断写入联机日志文件,联机日志文件总会有写满的时候。我们不可能让联机日志文件无限大,也不可能放无限多的联机日志文件,所以联机日志文件必须是循环使用的,在若干个日志文件中轮流的进行写入。一个日志文件写满以后转换到另外一个日志文件继续写的过程叫做日志切换(log switch)。

当一个联机日志文件写满时,可以选择将其归档为脱机日志文件,通常叫做归档日志文件。归档也就是拷贝,归档的过程也就是将写满的联机日志文件拷贝到预先指定的目录的过程。只有当一个联机日志文件完成归档以后,该联机日志文件才能够被再次循环使用。强烈建议在生产库中选择这种归档方式,只有在测试环境中可以不选择这种归档方式。

可以说,日志缓冲区和日志文件存在的唯一目的就是为了保证被修改的数据不会被丢失。反过来说,也就是为了能够在数据库崩溃的时候,可以用来将数据库恢复到崩溃的那个时间点上。这也就是说,只有将被修改的数据块的日志信息写入了联机日志文件以后,该被修改的数据块才可以说是安全的。如果日志信息在没有被写入日志文件时发生实例崩溃,这时对数据的修改仍将丢失。由此我们可以看出,将日志缓冲区中的日志信息写入日志文件是一个多么重要的过程,这个过程是由一个名为LGWR的后台进程完成的。LGWR承担了维护系统数据完整性的任务,它保证了数据在任何情况下都不会丢失。

触发LGWR进程将日志缓冲区中的日志信息写入联机日志文件条件包括以下几种:

<!--[if !supportLists]-->1)     <!--[endif]-->前台进程触发,包括两种情况。最显而易见的一种情况就是用户发出commit或rollback语句进行提交时,需要触发LGWR将内存里的日志信息写入联机日志文件,因为提交的数据必须被保护而不被丢失;另外一种情况就是在日志缓冲区中找不到足够的内存来放日志信息时,也会触发LGWR进程将一些日志信息写入联机日志文件以后,从而释放一些空间出来。

<!--[if !supportLists]-->2)     <!--[endif]-->每隔三秒钟,LGWR启动一次。

<!--[if !supportLists]-->3)     <!--[endif]-->在DBWR启动时,如果发现脏数据块所对应的重做条目还没有写入联机日志文件,则DBWR触发LGWR进程并等待LRWR写完以后才会继续。

<!--[if !supportLists]-->4)     <!--[endif]-->日志信息的数量达到整个日志缓冲区的1/3时,触发LGWR。

<!--[if !supportLists]-->5)     <!--[endif]-->日志信息的数量达到1MB时,触发LGWR。

<!--[if !supportLists]-->6)     <!--[endif]-->发生日志切换时触发LGWR

1.2 log buffer的内存结构

我们已经知道,日志缓冲区用来存放事务对数据块的改变的日志信息。那么这里的日志信息到底包含哪些内容,是由哪些结构组成的呢?

oracle记录数据库变化(也就是记录日志信息)的最小单位是改动向量(change vector)。改动向量用来描述对数据库中任何单个数据块所做的一次改动。改动向量的内容包括:被改动的数据块的版本号、事务操作代码、被改动的数据块的地址等。这里的版本号非常重要,它能够帮助数据块始终能够体现当前最新的状态。oracle在建立改动向量时,会从数据块中拷贝其版本号。而当恢复期间,oracle读取改动向量并将改动应用于相应的数据块以后,被恢复的数据块的版本号加1。这里的数据块可以属于表、也可以数据索引、也可以属于回滚段。但是对于临时表空间里的临时段,不会生成改动向量。

当多个改动向量按照先后顺序组合在一起,从而完成对数据库的一次改动时,oracle称这组改动向量为重做记录(redo record)。重做记录用来描述对数据库的一个原子改动。所谓原子改动,就是说,当应用改动中的改动向量时,要么全部成功,要么全部失败,不存在部分成功部分失败的情况。重做记录能够帮助整个数据库体现当前最新的状态。

一个事务至少产生一个重做记录,也可能产生多个重做记录。而oracle在应用日志记录进行恢复的过程中,以事务作为恢复的最小单位。要么恢复整个事务,要么回滚整个事务。也就是说,要么运用事务中的重做记录里的所有改动向量,要么一个改动向量都不运用。

因此,日志缓冲区就是许多重做记录按照发生的先后顺序组成的。同时,日志文件也就是由许多重做记录按照先后顺序排列在一起而组成的文件。

我们举个实例来说明重做记录和改动向量产生的过程。比如我们发出如下更新语句(假设表redo_test的name列上没有建立索引):

SQL> select * from redo_test;

       ID NAME

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

        1 abc

        2 abc

SQL> update redo_test set name='cdf' where id=1;

             该语句发出以后,会产生一个重做记录,用来描述对表的数据块的修改。包括下面三个改动向量:

<!--[if !supportLists]-->1)     <!--[endif]-->对回滚段事务表的改动,这发生在回滚段段头。事务表中包含被修改的数据块的地址、该事务的状态(commit或active)、以及存有该事务所使用的回滚段的地址。如果事务表被修改,就会产生针对于它的改动向量。

<!--[if !supportLists]-->2)     <!--[endif]-->对回滚段数据块的改动。将修改前的旧值(abc)存放到回滚段的数据块里。这时回滚段发生改变,于是产生改动向量。

<!--[if !supportLists]-->3)     <!--[endif]-->对redo_test表的数据块所做的改动。将修改后的新值(cdf)存放到表的数据块里。这时数据块发生改变,于是产生改动向量。

从这个实例可以知道,对于这个update事务,重做记录中会有三个改动向量。当然可能有其他情况会产生新的重做记录,比如修改的列如果有索引,则必须修改索引。这时就会产生第二个重做记录,用来描述对索引数据块的修改。这时候的重做记录还是和第一个重做记录一样,包含多个改动向量。此外,在事务完成之后运行commit或rollback语句时,就会产生第三个重做记录。该重做记录只有一个改动向量,用来记录对回滚段事务表的更改,因为commit或rollback时,需要更新事务表里记录的该事务的状态。

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

上一篇 /下一篇  2008-07-30 22:18:14 / 个人分类:工作技术

查看( 459 ) /评论( 2 ) / 评分( 5 / 0 )

1.3转储log buffer

oracle对很多内存结构都提供了转储到平面文件的功能,但是并没有直接提供转储日志缓冲区的功能。但是提供了转储日志文件的功能。我们前面已经知道,日志文件的内容就是日志缓冲区的完全拷贝,因此,转储日志文件就等于转储了日志缓冲区。

转储日志文件的命令为:

alter system dump logfile 'logfilename';

我们的实验过程如下。首先找到当前状态为CURRENT的日志文件是哪一个,以及它的全路径。

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

   GROUP# STATUS

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

        1 INACTIVE

        2 INACTIVE

        3 CURRENT

SQL> select member from v$logfile where group#=3;

MEMBER

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

/oracle/oradata/ora10g/redo03.log

然后,找到我们的redo_test表的object id,并进行更新操作。

SQL> desc redo_test

 Name                                     Null?   Type

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

 ID                                                NUMBER

 NAME                                              VARCHAR2(10)

SQL> select object_id from user_objects where object_name='REDO_TEST';

 OBJECT_ID

----------

    51367

SQL> select * from redo_test;

       ID NAME

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

        1 abc

        2 abc

SQL> update redo_test set name='cdf' where id=1;

SQL> commit;

             最后,我们转储出当前使用的日志文件。

SQL> alter system dump logfile '/oracle/oradata/ora10g/redo03.log';

打开生成的跟踪文件,从中找到含有51367(表redo_test的object id)的所有相关记录,如下图所示。

<!--[if !vml]--><!--[endif]-->                                                           图一

我们可以看到,第一行明确说明了,下面的内容都属于同一个重做记录(REDO RECORD)。

往下可以看到的CHANGE #的字样,这里的CHANGE #就表示改动向量(CHANGE VECTOR),我们可以看到一共生成了四个改动向量(第3、15、18、21行)。正像我们前面所描述的那样,第一个是对回滚段事务表的改动(CHANGE #2),第二个是对回滚段数据块的改动(CHANGE #4),第三个是对redo_test表的数据块的改动(CHANGE #1),第四个是提交时对回滚段事务表的改动(CHANGE #3)。注意,这里的CHANGE #后面的数值并不表示发生顺序,只不过是一个标识而已,发生的先后顺序要以SCN号为准,SCN号越小的表示越早发生。另外一个需要注意的是,在这个例子里,第四个提交时对回滚段事务表的改动(CHANGE #3)的改动向量并没有单独成为一个重做记录,这是因为我们发出update语句以后立即提交了。如果发出update语句以后,不立即提交,而是等待一段时间,然后再提交。这时转储出来的日志文件会显示出两个重做记录,最后的提交时对回滚段事务表的改动的改动向量会单独归到一个重做记录里去。后面会说明原因。

第8行的slot: 0表示被更新的记录位于数据块中的第一行。这与第35行的slot是一样的。如果我们发出“update redo_test set name='cdf' where id=2”时,就会发现这时slot为1。

第9行的size: 0表示修改后的值的长度减去修改前的值的长度的结果。而第36行的size表示修改前的值的长度减去修改后的值的长度的结果。由于我们这里修改前后的值的长度都是3,所以差额为0。如果我们发出“update redo_test set name='cdfg' where id=1”时,就会发现第9行的值为1,第36行的值为-1。

第11行的bdba表示redo_test表的数据块的地址,其实就是第3行的DBA所显示的地址。而hdba则表示redo_test表的segment header的地址。与第38行的值是相同的。第14行的col 1表示被更新的是表的第二列(第一列是col 0)。后面的[3]表示该列的值的长度,单位是字节。它们与第41行的内容是相同的。再后面的63 64 66则表示col 1列被更新后的值。我们来转换一下,如下所示。可以看到这正是我们更新后的“cdf”值。同样我们可以看到第41行的61 62 63,表示更新前的值,也就是“abc”。

SQL> select chr(to_number(63,'xx')),chr(to_number(64,'xx')),

 2 chr(to_number(66,'xx')) from dual;

C C C

- - -

c d f

第15行的DBA: 0x00800009表示回滚段事务表的地址,这与第18行的DBA是一样的。由于回滚段事务表位于回滚段段头里,所以这也就是回滚段段头的地址。008表示文件号乘以4,00009表示数据块地址。我们转储出该段头来看看里面放了些什么。

SQL> select to_number('008','xxx')/4 file#,

 2 to_number('00009','xxxxx') block# from dual;

    FILE#    BLOCK#

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

        2         9

SQL> alter system dump datafile 2 block 9;

我们打开所生成的跟踪文件,可以看到存放的正是回滚段事务表的信息。如下图二所示。我们可以看到粗体显示的正是我们发出的update语句在事务表中所登记的条目,state为9表示事务已经提交。

                                                                            图二

第17行的uba: 0x0080085a.0075.13表示存放redo_test被更新前旧值的回滚段数据块所在的地址,这与第20行的uba是一样的。0080085a表示回滚段数据块所在的地址,0075表示顺序号,13表示在回滚映射中的最后一个条目。我们只需要关心0080085a即可,其组成部分和前面所介绍过的是一样的,008表示文件号乘以4,0085a表示回滚段数据块的地址。

SQL> select to_number('008','xxx')/4 as file#,

 2 to_number('0085a','xxxxx') as block# from dual;

    FILE#    BLOCK#

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

        2      2138

SQL> alter system dump datafile 2 block 2138;

然后,打开生成的跟踪文件,找到含有51367(表redo_test的object id)的相关记录,如下图三所示。

                                   图三

从上图中我们可以看到熟悉的bdba和hdba。以及最后一行的61 62 63,这就是更新前的旧值。同时我们还可以注意到KDO Op code: 21,这表示事务所进行的操作的类型代码。因为我是在10g环境下测试的,所以这里显示了代码21,就表示进行的是update操作。如果在10g以前,会显示URP字样。

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

上一篇 /下一篇  2008-07-30 22:21:49 / 个人分类:工作技术

查看( 361 ) /评论( 0 ) / 评分( 0 / 0 )

<!--[if !supportLists]-->2.    <!--[endif]-->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])放入buffer cache,然后找出一个可用的回滚段数据块(假设为[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])放入buffer cache,并找到回滚段数据块(假设为[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又发出更新语句:update redo_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         commit SCN     timestamp

13     T18        2      189    -      -          18

14     T18        10     29     300    10         20

15     T18        commit SCN     timestamp

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

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

<!--[if !supportLists]-->1)     <!--[endif]-->从控制文件中得到下一个可用的联机日志文件。

<!--[if !supportLists]-->2)     <!--[endif]-->记录写入当前联机日志文件的最后一个日志块的SCN(叫做high SCN)。关闭当前联机日志文件。

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

<!--[if !supportLists]-->4)     <!--[endif]-->打开新的联机日志文件组中的所有成员,记录当前日志序列号(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

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

        1 CURRENT

        2 INACTIVE

        3 INACTIVE

SQL> create table t2 as select * from dba_objects;

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

   GROUP# STATUS

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

        1 ACTIVE

        2 CURRENT

        3 INACTIVE

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

SQL> alter system checkpoint;

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

   GROUP# STATUS

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

        1 INACTIVE

        2 CURRENT

        3 INACTIVE

可以看到,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分钟才实际开始写数据文件。

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

 

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

上一篇 /下一篇  2008-07-30 22:23:35 / 个人分类:工作技术

查看( 358 ) /评论( 0 ) / 评分( 0 / 0 )

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

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

<!--[if !supportLists]-->2)     <!--[endif]-->服务器进程构造一组改动向量(change vector)来描述对数据块所做的变化过程。这组改动向量会放在session的PGA中。实际上,这组改动向量也就是重做记录了。

<!--[if !supportLists]-->3)     <!--[endif]-->根据重做记录的大小,判断在日志缓冲区中需要多少空间。

<!--[if !supportLists]-->4)     <!--[endif]-->判断当前的SCN值,并将其存放到重做记录中。注意,并不是每个重做记录都具有不同的SCN值,不同的重做记录可能会共享相同的SCN值。

<!--[if !supportLists]-->5)     <!--[endif]-->进程获得名为redo copy的latch。

<!--[if !supportLists]-->6)     <!--[endif]-->进程获得名为redo allocation的latch。

<!--[if !supportLists]-->7)     <!--[endif]-->检查当前是否有其他进程生成了比当前持有的SCN更高的SCN值。如果是,则生成一个新的SCN值,并代替第四步所持有的SCN值。

<!--[if !supportLists]-->8)     <!--[endif]-->判断是否有足够的空间容纳当前的重做记录,这里足够的空间既包括日志缓冲区,也包括联机日志文件。这时的逻辑比较复杂。

<!--[if !supportLists]-->a)      <!--[endif]-->如果联机日志文件中有足够的空间,则判断日志缓冲区是否有足够的空间。

<!--[if !supportLists]-->                                                             I.     <!--[endif]-->如果日志缓冲区中没有足够的空间,则进程释放redo copy latch和redo allocation latch。然后在下面的两个选项中做一个选择:(1)如果这时已经有其他进程或其他条件触发了LGWR,则等待LGWR的完成;(2)如果这时LGWR没有启动,则触发LGWR启动。为了防止多个进程同时触发LGWR,oracle还引入了名为redo writing的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。

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

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

<!--[if !supportLists]-->9)     <!--[endif]-->当在日志缓冲区中写完重做记录以后,检查当前日志缓冲区中的重做记录的数量是否达到限定值,如果是,则必须触发LGWR进程。

<!--[if !supportLists]-->10)  <!--[endif]-->最后,进程更新buffer cache中的数据块。

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

图四

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

             当LGWR进程启动时,

<!--[if !supportLists]-->1)     <!--[endif]-->首先会尝试获取redo writing latch,以确保其他前台进程不会继续触发LGWR进程。

<!--[if !supportLists]-->2)     <!--[endif]-->然后会获取redo allocation latch,这是为了防止前提进程继续分配更多的日志缓冲区,否则日志缓冲区中的待写入日志文件的日志块不断增长,LGWR是无法确定到底应该写多少日志块的。

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

<!--[if !supportLists]-->4)     <!--[endif]-->在确定了要写哪些日志块以后,生成一个新的SCN号。

<!--[if !supportLists]-->5)     <!--[endif]-->LGWR释放redo allocation latch,并释放redo writing latch。

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

<!--[if !supportLists]-->7)     <!--[endif]-->LGWR将第4步生成的SCN号拷贝进待写的日志块的块头里,触发物理写操作,将这些待写日志块写入联机日志文件里去。

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

上一篇 /下一篇  2008-07-30 22:25:41 / 个人分类:工作技术

查看( 458 ) /评论( 0 ) / 评分( 0 / 0 )

3. log buffer的优化

我们一般不太会关注日志缓冲区的优化。这是因为日志缓冲区的争用一般不会是数据库的主要性能问题。前面我们已经知道,日志块的都是按照顺序往里写,不存在更新日志块的以前的内容,同时每个日志块大小都很小,所以几乎不会有多个进程抢同一个日志块的情况。其争用主要是进程由于找不到可用的日志块而必须等待的情况。而我们知道LGWR负责释放脏的日志块从而提供可用日志块,LGWR在日志缓冲区中的脏日志块超过1M或者超过日志缓冲区的1/3时就会启动,而且在将重做记录写入联机日志文件时,都是按照顺序写入,不存在类似DBWR的随机写入,所以写入的速度是非常快的,除非硬盘的I/O速度有很大的问题。可以执行下面的SQL语句来判断硬盘的I/O是否过慢。

SQL > select total_waits,time_waited,average_wait,time_waited/total_waits as avg

 2 from v$system_event where event = 'log file parallel write';

TOTAL_WAITS TIME_WAITED AVERAGE_WAIT       AVG

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

 314346633  129581305           0 .41222425

我们可以看到,AVERAGE_WAIT表示LGWR完成一次写入平均需要多少时间,是用等待时间除以等待次数得出的、并四舍五入以后得到的平均值(AVG是没有四舍五入以后的值)。如果AVERAGE_WAIT大于1,就表示硬盘的I/O比较慢。

不过,对于整个数据库的健康检查来说,还是需要衡量一下数据库的日志缓冲区是否存在健康隐患。所以也还是需要了解一些有关日志缓冲区性能的一些指标。

3.1 log buffer的统计信息

有关log buffer的统计信息,我们都可以从v$sysstat里找到。我们可以运行一个DML语句,然后比较前后的统计信息,看看都发生了哪些变化。

SQL> select name,value from v$sysstat where name like '%redo%' order by name;

NAME                                                                 VALUE

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

redo blocks read for recovery                                          110

redo blocks written                                                  13617

redo buffer allocation retries                                           0

redo entries                                                         16868

redo log space requests                                                  0

redo log space wait time                                                 0

redo log switch interrupts                                               0

redo ordering marks                                                      1

redo size                                                          6228264

redo subscn max counts                                                   0

redo synch time                                                        207

redo synch writes                                                     2475

redo wastage                                                        519976

redo write time                                                       1105

redo writer latching time                                                0

redo writes                                                           1991

 

SQL> update redo_test set name='cdf' where id=1;

SQL> commit;

SQL> select name,value from v$sysstat where name like '%redo%' order by name;

NAME                                                                 VALUE

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

redo blocks read for recovery                                          110

redo blocks written                                                  13619

redo buffer allocation retries                                           0

redo entries                                                         16869

redo log space requests                                                  0

redo log space wait time                                                 0

redo log switch interrupts                                               0

redo ordering marks                                                      1

redo size                                                          6228856

redo subscn max counts                                                   0

redo synch time                                                        207

redo synch writes                                                     2476

redo wastage                                                        520376

redo write time                                                       1105

redo writer latching time                                                0

redo writes                                                           1992

             我们可以看到有些统计信息发生了变化,而有些则没有。这些统计信息都是累计值,自从实例启动以

来就一直累加而产生的。我们依次解释一些重要的统计信息。

<!--[if !supportLists]-->1)     <!--[endif]-->redo writes、redo blocks written、redo write time:这三个统计信息是主要的统计信息,在LGWR写完重做记录以后更新,而且只能由LGWR负责更新。redo writes表示写了多少次,我们可以看到上例中写了1次(1992-1991);redo blocks written表示总共写了多少日志块,可以看到写了2个日志块(13619-13617),由此我们可以知道平均写一次可以写2个日志块。redo write time表示将重做记录写入日志文件花了多少时间,单位是10个毫秒,我们可以看到为了写这2个日志块所需要的时间都几乎为0(1105-1105)。

<!--[if !supportLists]-->2)     <!--[endif]-->redo size、redo entries:这两个统计信息是在重做记录拷贝到日志缓冲区之前更新的。它们都表示产生了多少量的重做记录,redo size以字节为单位,而redo entries以个数为单位。比如,上例中产生了592(6228856-6228264)个字节的重做记录,共1(16869-16868)个重做记录,平均每个重做记录为592个字节。

<!--[if !supportLists]-->3)     <!--[endif]-->redo wastage:该记录表示当日志缓冲区的日志块被写入日志文件时,日志块中没有被利用的空间数量,以字节为单位。因为当把重做记录拷贝到日志缓冲区中的日志块时,需要格式化日志块以后才能实际存放日志信息,这样就会“浪费”一些日志缓冲区空间。上例中,我们可以看到为了格式化日志块而浪费了大约400(520376-519976)个字节的空间。

注意:上例中,我们写了2个日志块(redo blocks written)。我们知道一个日志块的大小是512个字节,那么两个日志块的应该占用1024个字节。但是实际重做记录只占了592个字节(redo size)。其中,为了格式化日志块而“浪费”了400个字节(redo wastage)。同时每个日志块头需要16个字节,两个日志块就是32个字节。那么我们有:592+400+32=1024。这正是两个日志块的容量。由此我们可以推导出这样的公式:redo blocks written×redo block size=16×redo blocks written+redo size+redo wastage

    由此我们可以看出,oracle为了更新3个字节('cdf',需要消耗1024个字节的日志文件的空间。看来oracle在记录日志方面,确实是比较消耗磁盘空间的。所以对于更新频繁的系统而言,产生的日志量会非常大。

<!--[if !supportLists]-->4)     <!--[endif]-->redo log space requests、redo log space wait time:redo log space requests表示对联机日志文件空间请求的次数,redo log space wait time表示在发出请求空间以后的等待时间。这两个统计信息只有在前台进程请求联机日志文件空间未果的情况下才会增加,这时前台进程等待日志切换完成。在没有人为的发出alter system switch logfile命令的前提下,redo log space requests就表示日志切换的总次数。

<!--[if !supportLists]-->5)     <!--[endif]-->redo buffer allocation retries:表示再次尝试在日志缓冲区中分配可用空间的次数。当进程第一次没能在日志缓冲区中获得可用空间时,该进程必须等待LGWR刷新日志缓冲区或者等待日志切换完成等,然后会再次尝试获取空间。理想情况下,该统计信息应该为0。

注意:这里我们可以获得在拷贝到日志块时必须等待的重做记录的数量所占的比例,计算公式为:redo buffer allocation retries / redo entries。该比例应该接近于0,不应大于1%。如果这个值不断变大则说明服务器进程在获得日志块之前必须等待,这时应该增加日志缓冲区,或者提高LGWR写的效率,也就是提高硬盘物理I/O的速度。

<!--[if !supportLists]-->6)     <!--[endif]-->redo synch writes,redo synch time:这两个统计信息是在用户每次提交(commit)时增加。redo synch writes表示由于提交而刷新日志缓冲区的次数,而redo synch time表示由于提交而刷新日志缓冲区所花的时间,以10个微妙为单位。

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

上一篇 /下一篇  2008-07-30 22:26:21 / 个人分类:工作技术

查看( 348 ) /评论( 0 ) / 评分( 0 / 0 )

3.2 log buffer的等待事件

3.2.1 log file sync等待事件

当用户发出提交或回滚语句时会触发LGWR将重做记录写入联机日志文件,这种触发LGWR的方式叫做同步写(sync writes)触发,而其他剩下的触发LGWR的方式叫做后台写(background writes)。log file sync等待事件只与sync writes有关,而log file parallel write等待事件只与background writes有关。

举例来说,一个用户进程可能进行一个非常大的事务,该事务会产生非常多的重做记录,从而引起很多的background writes。不过,用户session所运行的事务永远不会等待这些background writes完成以后才继续进行。一旦用户进程提交或回滚了事务,那么用户进程将触发LGWR,并且等待log file sync等待事件,一直等到LGWR将当前的重做记录,包括提交或回滚标记全都写入联机日志文件里为止。在这个日志同步(log synchronization)的过程中(将日志缓冲区中的重做记录写入联机日志文件的过程通常也叫做日志同步过程),LGWR进程等待sync write结束,这时它的等待事件为:log file parallel write,而用户session也在等待sync write结束,而这时它等待的等待事件为:log file sync事件。

一旦进程开始等待log file sync事件,用两种方式退出该等待:

<!--[if !supportLists]-->1)    <!--[endif]-->当日志缓冲区里的重做记录都写入联机日志文件以后,由LGWR触发,告诉前台进程。

<!--[if !supportLists]-->2)    <!--[endif]-->当等待事件超时(通常是1)时,前台进程检查当前的log SCN,来判断当前进程的提交是否已经将重做记录写到了联机日志文件里了。如果已经写好了,前台进程继续进行,否则前台进程继续等待log file sync事件。

通常,log file sync等待事件是可以不用过多考虑的。但是如果很多session都在等待该事件的话,就会影响系统的整体响应时间了。我们来模拟一下这两个等待事件。

SQL> create table t1(id number,name varchar2(20)) as select rownum as rid,'abcdef'

 2 from dba_objects where rownum<=40000;

SQL> connect / as sysdba

Connected.

SQL> startup force

             在运行测试脚本前,先看看统计信息和等待事件各是什么。


SQL> select name,value from v$sysstat where name in('redo entries','redo size',

 2 'redo synch writes','redo wastage','redo write time','redo writes') order by name;

NAME                                                                 VALUE

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

redo entries                                                           375

redo size                                                           185160

redo synch writes                                                       14

redo wastage                                                          9552

redo write time                                                        131

redo writes                                                             41

SQL> select SID,EVENT,TOTAL_WAITS WAIT_CLASS from v$session_event

 2 where sid in(159,166) and event in ('log file sync','log file parallel write');

   no rows selected

SQL> begin

 2 for x in(select rowid rid,t1.* from t1)

 3    loop

 4             update t1 set name=lower(name) where rowid=x.rid;

 5             commit;

 6     end loop;

  7 end;

 8 /

             在运行测试脚本以后,再来看看统计信息和等待事件发生了哪些变化。

SQL> select name,value from v$sysstat where name in('redo entries','redo size',

 2 'redo synch writes','redo wastage','redo write time','redo writes') order by name;

NAME                                                                 VALUE

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

redo entries                                                         49586

redo size                                                         20031044

redo synch writes                                                       18

redo wastage                                                       2382452

redo write time                                                        996

redo writes                                                           5986

SQL> select distinct sid from v$mystat;

      SID

----------

      159

SQL> select c.sid,a.name from v$bgprocess a ,v$process b , v$session c

 2 where a.paddr=b.addr and b.addr = c.paddr and a.name='LGWR';

      SID NAME

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

      166 LGWR

SQL> select SID,EVENT,TOTAL_WAITS WAIT_CLASS from v$session_event

 2 where sid in(159,166) and event in ('log file sync','log file parallel write');

      SID   EVENT                TOTAL_WAITS WAIT_CLASS

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

     159 log file sync                  3   Commit

     166 log file parallel write     6007   System I/O

从上面的测试,我们可以明显看到,通过在循环中不断进行提交,出现了3log file sync等待事件。而实际上,过于频繁的提交也正是log file sync等待事件出现的主要原因。而log file parallel write等待事件则总是会出现的,只要LGWR开始刷新日志缓冲区,该进程就会等待log file parallel write等待事件,是不可避免的,不用过多关注该等待事件。同时,我们可以看到,由于过多的提交,导致生成的重做记录的数量非常巨大,redo size1984588420031044-185160)字节,也就相当于将近19M

注意:10g以后在v$system_event和v$session_event中会由字段wait_class显示等待事件所属的类别。也就是该等待事件是由于什么原因引起的。上面我们可以看到log file sync是由于提交引起的,log file parallel write是由于进行物理I/O引起的。

我们来修改一下上面的脚本,将commit移出循环以后,再按照上面的测试步骤执行一遍。可以发现log file sync等待事件只会出现1次,也就是说减小了一半的log file sync的等待。同时,通过将commit移出循环,所产生的重做记录的数量也下降到大约10M,也就是说减少了将近一半的日志量。

由此,我们也就可以知道,要减小log file sync的主要方法就是减少提交或回滚的次数。同时,过于频繁的提交不光会引起log file sync等待事件,而且很有可能引起ora-01555snapshot too old错误。我们可以使用下面的SQL语句来查找log file sync等待所占的总共等待时间的百分比,以及每个session平均每个小时提交多少次,进而可以找到该session执行了哪些SQL语句引起了过于频繁的提交。

select a.sid,

      a.event,

      a.time_waited,

      a.time_waited / c.sum_time_waited * 100 pct_wait_time,

      d.value user_commits,

      round((sysdate - b.logon_time) * 24) hours_connected

from  v$session_event a, v$session b, v$sesstat d,

      (select sid, sum(time_waited) sum_time_waited

       from  v$session_event

       where event not in ('Null event',

                   'client message',

                   'KXFX: Execution Message Dequeue - Slave',

                   'PX Deq: Execution Msg',

                   'KXFQ: kxfqdeq - normal deqeue',

                   'PX Deq: Table QNormal',

                   'Wait for credit - send blocked',

                   'PX Deq Credit: send blkd',

                   'Wait for credit - need buffer to send',

                   'PX Deq Credit: need buffer',

                   'Wait for credit - free buffer',

                   'PX Deq Credit: free buffer',

                   'parallel query dequeue wait',

                   'PX Deque wait',

                   'Parallel Query Idle Wait - Slaves',

                   'PX Idle Wait',

                   'slave wait',

                   'dispatcher timer',

                   'virtual circuit status',

                   'pipe get',

                   'rdbms ipc message',

                   'rdbms ipc reply',

                   'pmon timer',

                   'smon timer',

                   'PL/SQL lock timer',

                   'SQL*Net message from client',

                   'WMON goes to sleep')

       having sum(time_waited) > 0 group by sid) c

where a.sid        = b.sid

and   a.sid        = c.sid

and   a.sid        = d.sid

and   d.statistic# = (select statistic#

                       from  v$statname

                       where name = 'user commits')

and   a.time_waited > 10000

and   a.event      = 'log file sync'

order by pct_wait_time, hours_connected;

如果应用程序已经确保了使用正确的频率进行提交,则还是发现很严重的log file sync等待事件,则比较常见的原因主要包括:

<!--[if !supportLists]-->1)     <!--[endif]-->联机日志文件放在一个很慢的磁盘上。可以用前面我们提到的log file parallel write的平均等待时间应该小于等于10个毫秒来判断,磁盘速度是否过慢。

<!--[if !supportLists]-->2)     <!--[endif]-->联机日志文件与其他随机写入的文件放在了同一个磁盘上。从前面已经知道,联机日志文件都是顺序写入的。不能够将其与其他随机写入的文件放在一起,需要存放在单个的磁盘上。

<!--[if !supportLists]-->3)     <!--[endif]-->联机日志文件放在了RAID 5的磁盘组里。RAID 5适合频繁的读取,比如数据仓库类的应用等,但不适合频繁写入。

最好能够将每个联机日志文件都放在单独的小的、转速快的磁盘上,与数据文件所在的磁盘阵列分开。

如果可以的话,最好不要使用文件系统来管理磁盘,直接使用RAW设备,直接交给oracle来管理磁盘。而且,oracle中最适合放在RAW设备上的文件就是联机日志文件。

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

上一篇 /下一篇  2008-07-30 22:26:50 / 个人分类:工作技术

查看( 645 ) /评论( 0 ) / 评分( 0 / 0 )

3.2.2其他相关的等待事件

       当进程需要向日志缓冲区里拷贝重做记录时,发现没有足够的可用空间时,则必须等待log buffer space事件。如果一个进程花费了太多的时间在log buffer space等待事件上,这通常是由于下面两个原因:
<!--[if !supportLists]-->1)     <!--[endif]-->日志缓冲区尺寸太小。对于一个繁忙的批处理数据库系统里,一个太小的日志缓冲区(小于512K)会导致进程等待log buffer space
<!--[if !supportLists]-->2)     <!--[endif]-->LGWR进程太慢了,这也就是物理I/O速度过慢。可以通过检查log file parallel write等待事件的平均等待时间来判断,如果大于10毫秒,则说明LGWR过慢。
可以通过增加日志缓冲区的尺寸或加快LGWR的进程来减少log buffer space等待事件。
 
       而较小尺寸的联机日志文件会引起log file switch completionlog file switch (checkpoint incomplete)
这两个等待事件。log file switch completion表示进程正在等待日志切换完成。而当应用程序产生很多重做,然后由LGWR进程非常快得写日志文件并进行日志切换时,这时DBWR进程还没有把脏数据块写入磁盘以通知checkpoint结束时,这个时候产生log file switch (checkpoint incomplete)等待事件。可以通过增加联机日志文件的尺寸来减少log file switch completionlog file switch (checkpoint incomplete)
 
       使用如下SQL来显示有关日志缓冲区的latch freemisrate是否很大。

select name,gets,misses,to_char((misses/(gets+misses)) * 100,'990.99') misrate

from v$latch where name in ( 'redo allocation' , 'redo copy', 'redo writing')

如果是redo copy的丢失率很严重,则可以考虑增加隐藏参数:_log_simultaneous_copies的值。该参数定义了redo copy latch的数量,该参数缺省为CPU数量的两倍。从8i起,该参数成为隐藏参数,因为不适合为系统增加过多的redo copy latch,特别是在OLTP环境中。增加该参数可以减少前台对redo copy latch的等待,但是要注意,过多的redo copy latch可能会使得LGWR等待更长的时间。因为前面我们已经知道,LGWR在确定了要写哪些日志块以后,会等待前台进程完成对这些日志块的操作以后,才开始正式写入。所以,如果很多的前台进程都可以获得redo copy latch以后,就可能引起LGWR要等很长时间才能开始正式写入。

如果是redo allocation的丢失率很严重,需要考虑是否有可能减少重做记录的生成。具体可以看下面有关如何减少重做记录的部分。8i以前,存在一个参数:log_small_entry_max_size,该参数说明了当重做记录的尺寸小于该参数指定值时,使用redo allocation latch来保护重做记录拷贝到日志缓冲区的过程,否则,如果重做记录大于该参数值时,使用redo copy latch来保护这个过程。但是从8i以后,取消了该参数,只要拷贝重做记录到日志缓冲区,就获得redo copy latch

如果是redo writing的丢失率很严重,则说明LGWR写的时间过长,导致其他进程无法获得该latch。这时可以通过减小_log_io_size来增加LGWR写的频率。具体_log_io_size的用法可以参考下面log buffer的设置部分。

3.3 log buffer的设置

对于日志缓冲区来说,设置过小,容易引起log buffer space等待事件。但也不是说设置的越大就越好的,设置过大,由于LGWR进程会不断启动刷新日志缓冲区从而释放内存,所以可能会根本用不上多余的内存,从而浪费内存。

设置合适的日志缓冲区大小,目的是为了能够让LGWR进程合理的触发。理想情况下是,一方面,在LGWR进程向联机日志文件中写重做记录时,日志缓冲区中还是有剩余的可用空间以供其他进程所使用;另一方面,当LGWR进程完成时,日志缓冲区中的剩余可用空间不要很多,因为这时LGWR所写入日志文件的日志块就可以释放出来了,成为新的剩余可用空间。然后,LGWR可以再次启动刷新脏的日志块。如此良性循环,就能在满足性能的前提下,充分利用日志缓冲区。没必要盲目的把日志缓冲区设置的很大,完全可以把节省下来的内存交给比如数据块缓冲区(buffer cache)等这样更需要内存的组件。

我们已经知道,当重做记录达到日志缓冲区的1/31M时,就会触发LGWR进程。也就是说,oracle缺省认为LGWR进程在写日志缓冲区大小的1/31M的重做记录的过程中,剩下的日志缓冲区可以供新的重做记录的需要。当LGWR写完以后,那么这1/31M的日志缓冲区就又可以成为可用的日志块以容纳新的重做记录了。由此,我们可以很容易推导出,当我们设置日志缓冲区达到3M3×1M)以上时,这时多余出来的日志缓冲区实际上并不能用得上,换句话说,多余出来的内存就被我们浪费了。

不过,本质上,这个启动LGWR的限度值是由一个隐藏参数:_log_io_size决定的,如下所示。该参数表示日志缓冲区中存在多少个脏日志块时触发LGWR进程写脏日志块。缺省情况下,该参数为0,表示当脏日志块占日志缓冲区的1/3时触发LGWR进程。如果设置了该参数为一个非0值,则如果该参数值不大于日志缓冲区大小的1/2时,该参数值作为启动LGWR的限度值。否则,如果该参数值大于日志缓冲区的1/2时,忽略该参数值,以日志缓冲区大小的1/2为启动LGWR的限度值。不管怎么样,脏日志块的容量只要超过1M,就必然触发LGWR进程。

SQL> select x.ksppinm, y.ksppstvl, x.ksppdesc from x$ksppi x , x$ksppcv y

 2 where x.indx = y.indx and ksppinm like '%_log_io_size%';

KSPPINM      KSPPSTVL  KSPPDESC

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

_log_io_size 0         automatically initiate log write if this many redo blocks in buffer

在设置日志缓冲区时,可以参考下面这个建议的公式来计算:1.5×(平均每个事务所产生的重做记录大小×每秒提交的事务数量)

首先先找到总事务量是多少:

select a.value as trancount from v$sysstat a,v$statname b

where a.statistic# = b.statistic# and b.name = 'user commits';

然后,找到系统总共的运行时间:

select trunc(sysdate - startup_time)*24*60*60 as seconds from v$instance;

             第三,找到所产生的所有重做记录大小:

select value as redoblocks from v$sysstat where name = 'redo blocks written';

最后,我们可以分别计算公式中的值:平均每个事务所产生的重做记录大小= redoblocks/trancount;每秒提交的事务数量=trancount/seconds。这样,最后所建议的日志缓冲区的大小可以写为:

1.5* (redoblocks/trancount)* (trancount/seconds)

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

上一篇 /下一篇  2008-07-30 22:27:28 / 个人分类:工作技术

查看( 384 ) /评论( 0 ) / 评分( 0 / 0 )

3.4减少生成的重做记录的方法

要优化日志缓冲区的使用,最直接、最容易看到效果的方法当然就是阻止重做记录的产生了。也就是说对DML语句不记录数据块的改变过程,从而减少对日志缓冲区的使用。但是,毫无疑问,这完全违背了oracle设立日志缓冲区以及联机日志文件的目的。由于没有产生对数据块变化的记录,那么如果数据库发生崩溃,将导致无法恢复到数据库崩溃前一秒的状态,也就必然发生数据丢失。

通过使用NOLOGGING短语或者append提示来阻止重做记录的产生。该短语可以用在表空间上,表示该表空间里的对象缺省都不产生日志。也可以将表或者索引定义为NOLOGGING,这样该表上的DML操作都不生成重做记录,而对索引的重建也不生成重做记录。还可以直接用在以下的SQL语句中:

<!--[if !supportLists]-->1)     <!--[endif]-->直接路径装载(Direct Loader)以及直接路径插入(对insert使用append提示:insert /*+ append*/)。

<!--[if !supportLists]-->2)     <!--[endif]-->create table table-name NOLOGGING as select …(CTAS

<!--[if !supportLists]-->3)     <!--[endif]-->alter table table-name NOLOGGING move tablespace tablespace-name

<!--[if !supportLists]-->4)     <!--[endif]-->create index index-name NOLOGGING …

<!--[if !supportLists]-->5)     <!--[endif]-->alter index index-name NOLOGGING rebuild…

<!--[if !supportLists]-->6)     <!--[endif]-->分区操作中,可以添加NOLOGGING短语。

<!--[if !supportLists]-->7)     <!--[endif]-->truncate命令总是以NOLOGGING方式执行。

比如,我们来看一个在SQL语句中添加NOLOGGING选项的例子,数据库运行在归档模式下。

SQL> select value from v$mystat a,v$statname b

 2 where a.statistic#=b.statistic# and b.name='redo size';

    VALUE

----------

    10592

SQL> create table t1 as select * from dba_objects;

SQL> select value from v$mystat a,v$statname b

 2 where a.statistic#=b.statistic# and b.name='redo size';

    VALUE

----------

  5796480

             很明显的,正常情况下创建表时生成了5785888(5796480-10592)字节,也就是约5.5M的重做记录。

SQL> drop table t1;

SQL> select value from v$mystat a,v$statname b

 2 where a.statistic#=b.statistic# and b.name='redo size';

    VALUE

----------

  5801424

SQL> create table t1 NOLOGGING as select * from dba_objects;

SQL> select value from v$mystat a,v$statname b

 2 where a.statistic#=b.statistic# and b.name='redo size';

    VALUE

----------

  5909952

可以看到,当使用NOLOGGING选项创建表时,只生成了108528(5909952-5801424)字节,也就是约106K的重做记录。原来生成的大约5.5M日志就是表t1本身的容量,而使用NOLOGGING以后,直接写入磁盘,没有对表t1自身的数据记录日志,所生成的106K的重做记录只是在创建表时,对所修改的数据字典的保护。不过这里要注意一个问题,就是我们的测试环境是归档模式,如果在非归档模式下,则对CTAS命令使用NOLOGGING选项所产生的日志与不使用NOLOGGING选项所产生的日志几乎没有差别。这是因为非归档模式下的CTAS命令本身就不会对所创建的表的数据记录日志。

现在我们来测试一下在非归档模式下,使用NOLOGGING选项来定义索引时的情况。

SQL> select index_name,logging from user_indexes where table_name='T1';

INDEX_NAME                    LOG

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

IDX_T1_OBJECTNAME             YES

SQL> select value from v$mystat a,v$statname b

 2 where a.statistic#=b.statistic# and b.name='redo size';

    VALUE

----------

  4170024

SQL> alter index idx_t1_objectname rebuild;

SQL> select value from v$mystat a,v$statname b

 2 where a.statistic#=b.statistic# and b.name='redo size';

    VALUE

----------

  6257600

             可以看到,正常重建索引时生成了2087576(6257600-4170024)字节,也就是大约2M的重做记录。

SQL> alter index idx_t1_objectname nologging;

SQL> select value from v$mystat a,v$statname b

 2 where a.statistic#=b.statistic# and b.name='redo size';

    VALUE

----------

  6259968

SQL> alter index idx_t1_objectname rebuild;

SQL> select value from v$mystat a,v$statname b

 2 where a.statistic#=b.statistic# and b.name='redo size';

    VALUE

----------

  6325568

将索引定义为NOLOGGING以后,再次rebuild只生成了65600(6325568-6259968)字节,也就是64K的重做记录。注意,这是在非归档模式下的测试结果。可以看到这与CTAS创建表有所不同,CTAS在非归档模式下不会记录数据的变化,只会记录数据字典的变化。而在非归档模式下创建索引时,如果不使用NOLOGGING选项,则既会记录数据字典的变化,也会记录索引数据块的变化。

现在,我们将前面那个CTAS例子中(归档模式下)的当前的联机日志文件(含有用NOLOGGING选项创建表t1的重做记录)转储出来看看这时的重做记录是怎样的。我们找到表t1对应的object id为51535,然后对转储出来的日志文件进行搜索,可以发现很多类似下图五这样的内容,这部分内容表示对数据字典的修改所生成的重做记录。

                                        图五

我们同时还会发现很多类似下图六的内容,这部分内容就说明为何使用了append以后,并没有修改数据字典,为何还是产生了大约106K的重做记录的原因。不管使用NOLOGGING还是append,都会对所修改

                                         图六

的数据块进行标记,标记这些数据块已经出现软损坏(soft-corrupt)了,在恢复是不能使用。这时改动向量的类型是INVLD。同时会记录起始数据块的地址(下图的DBA:0X1000114),以及后面有多少个数据块(下图的BLKS:0x0005)被标记为损坏。当使用这样的重做记录进行恢复时,系统会报ORA-273错误。

4.归档

当一个联机日志文件写完,发生日志切换,切换到另一个日志文件时,可以选择将前一个写完的联机日志文件进行归档。归档的过程简单理解就是拷贝的过程,将前一个联机日志文件完整拷贝到指定的地方。这样的话,每次一个联机日志文件写完以后就生成一个拷贝出来,从而可以使得自设置归档以来数据库所发生的所有的数据块变化都被记录下来。从而为数据恢复提供了完备的基础,你可以将数据库恢复到任意你希望的时间点。不过设置归档需要比不设置归档消耗更多的磁盘空间,同时会一定程度上降低系统的性能,因为毕竟多了一块写磁盘的工作。但是,这部分性能的损失一方面相对数据安全性来说,完全可以忽略;另一方面,这部分的性能损失本身不会对系统产生很大的影响,而且可以通过一些方式将归档所带来的性能损失降低到最低。

如果设置了归档模式,那么当一个联机日志文件文件还没有完成归档时,是不能被重用的。也就是说,假设只有两组归档日志(A和B),假设A写完以后,切换到B以后,B也很快写完,这时又切换回A时,发现A仍然还处于归档过程中,这时LGWR进程必须等待,而触发LGWR的用户进程也必须等待。其等待事件为log file switch (archiving needed)。这时整个数据库都停住了。

归档主要是通过ARCH进程来完成的,只有将数据库设置为archivelog模式时,oracle才会启动ARCH进程。设置归档模式时,先启动数据库到mount模式,然后发出alter database archivelog就可设置数据库为归档模式。同时发出命令:archive log start或者设置初始化参数log_archive_start为true时,表示系统自动进行归档,否则需要手工归档。ARCH进程在以下条件下触发:

<!--[if !supportLists]-->1)     <!--[endif]-->如果设置了自动归档模式,则日志切换时,由LGWR进程触发ARCH进程进行归档。这是最常见的方式。

<!--[if !supportLists]-->2)     <!--[endif]-->可以手工进行归档。使用命令:alter system archive log current表示启动ARCH进程,从而对当前的日志文件进行归档。

<!--[if !supportLists]-->3)     <!--[endif]-->如果ARCH进程在5分钟以后还没有接收到LGWR的通知,则发生超时,于是ARCH被唤醒以检查是否存在需要归档的日志文件。ARCH通过读取控制文件中的信息来决定是否需要归档以及应该归档哪些日志文件。但是在进行实例恢复或者介质恢复的过程中,ARCH进程不会启动。

通常来说,ARCH进程启动以后的处理过程包括:

<!--[if !supportLists]-->1)     <!--[endif]-->从控制文件中读取未归档的日志文件。注意,可能会有多个日志文件。

<!--[if !supportLists]-->2)     <!--[endif]-->在内存中分配归档日志块,归档日志块的个数由隐藏参数_log_archive_buffer确定;每个归档日志块的大小则由_log_archive_buffer_size确定。

<!--[if !supportLists]-->3)     <!--[endif]-->打开联机归档日志组中的所有的日志文件,并校验它们的文件头。

<!--[if !supportLists]-->4)     <!--[endif]-->搜索可用的归档目的地。

<!--[if !supportLists]-->5)     <!--[endif]-->在可用的归档目的地创建并打开归档日志文件。

<!--[if !supportLists]-->6)     <!--[endif]-->在日志文件中从头到尾进行循环,取出每个日志块,并放入内存中的归档日志块,然后按照SCN的顺序放入归档日志文件中。

<!--[if !supportLists]-->7)     <!--[endif]-->关闭所有打开的文件,包括联机日志文件和归档日志文件。

在ARCH进程将联机日志文件里的日志块读取到归档日志块里的时候,如果发现所读取的日志块发

生损坏,则会转到相同日志组中的另外一个日志文件去读取相同的日志块。如果相同日志组中的所有日志文件的该日志块都发生损坏,则ARCH进程报错,无法继续进行归档工作。

      当ARCH进程从日志文件中成功读取一个日志块以后,为了平衡物理I/O,ARCH进程会转到相同日志组中的另外一个日志文件读取下一个日志块。如此这般,在日志组中的每个日志文件之间进行循环读取。

      如果ARCH进程出现性能问题,最突出的表现就是log file switch(archiving needed)等待事件的等待次数和等待时间很长。我们先来模拟一下该等待事件。这里要说明一下,在设置手动归档时,10g以前都只要archive log stop就可以了。10g以后必须使用alter database archivelog manual命令。

SQL> startup mount;

SQL> alter database archivelog manual; --设置手动归档,不启用自动归档

SQL> alter database open;

SQL> select distinct sid from v$mystat;

      SID

----------

      159

SQL> insert into hanson.t1 select * from dba_objects;

            这时,该语句停住了,然后我们再开一个session,看看该session在等待什么事件。

SQL> select event from v$session_wait where sid=159;

EVENT

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

log file switch (archiving needed)

             可以看到,该进程正是在等待log file switch(archiving needed)事件。

调优ARCH时,可以从以下几个方面入手。

<!--[if !supportLists]-->1)     <!--[endif]-->确定是否启用了自动归档,如果没有启用自动归档,则必须定期手工归档。

<!--[if !supportLists]-->2)     <!--[endif]-->需要确保归档日志文件不应该与联机归档日志文件放在同一个磁盘阵列里,而应该单独存放。同时避免把归档日志文件放在RAID-5设备上。

<!--[if !supportLists]-->3)     <!--[endif]-->把联机日志文件设置大一些,以便给ARCH进程的归档工作留出足够的时间。

<!--[if !supportLists]-->4)     <!--[endif]-->应该检查一下归档路径的设置,如果设置了很多的归档路径,则可以考虑减少一些归档路径。

<!--[if !supportLists]-->5)     <!--[endif]-->如果ARCH进程的速度还是很慢,则可以考虑增加ARCH进程的数量。可以设置的最大数量随oracle版本的不同而不同,9i下可以设置10个,而10g下可以设置30个。初始化参数log_archive_max_processes决定了最多能够启动多少个ARCH进程。当LGWR进程发现当前的ARCH进程的数量不足以支持当前的负载时会自动启动一个新的ARCH进程。LGWR每次启动一个新的ARCH进程时都会将该信息记录在日志文件里。但是这里要说明的是,并不是说多个ARCH进程可以同时读取同一组联机日志文件并对其规定,而是说次只能有一个ARCH进程来读取联机日志文件并将其归档到不同的归档路径下。因此假设4组联机日志文件,依次为A、B、C、D。同时定义log_archive_max_processes为3,也就是最多能够启动3个ARCH进程,假设分别为1#、2#、3#。那么当A写完,发生日志切换,切换到B时,LGWR启动1# ARCH进程对A进行归档。假设B也写完了,再次发生日志切换,切换到C时,这时ARCH 1#还没完成归档,这时LGWR发现这个情况,于是启动2# ARCH进程对B进行归档。假设这时C也写完了,再次发生日志切换,切换到D时,如果这时1# ARCH进程已经完成了对A的归档,则LGWR启动1# ARCH来对C进行归档,如果这时1# ARCH仍然没有完成,则LGWR会启动3# ARCH对C进行归档。从这里我们已经可以看到,通常情况下,log_archive_max_processes的缺省值就够用了。如果将要发生很大量的数据库批量写入以及更新等操作,则说明可能在很短时间内涌入大量的重做记录,可能日志切换的速度会比ARCH进行归档的速度,这个时候,可以设置log_archive_max_processes为一个更大的值,等到批量写入完成以后,再将该参数设置回缺省值。

 

原创粉丝点击