技术开发 频道

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

......接上篇【IT168技术文档】
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
    我们可以看到有些统计信息发生了变化,而有些则没有。这些统计信息都是累计值,自从实例启动以
来就一直累加而产生的。我们依次解释一些重要的统计信息。

1) 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)。

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

3) 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在记录日志方面,确实是比较消耗磁盘空间的。所以对于更新频繁的系统而言,产生的日志量会非常大。

4) 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就表示日志切换的总次数。

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

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

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

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事件,用两种方式退出该等待:
1) 当日志缓冲区里的重做记录都写入联机日志文件以后,由LGWR触发,告诉前台进程。

2) 当等待事件超时(通常是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
    从上面的测试,我们可以明显看到,通过在循环中不断进行提交,出现了3次log file sync等待事件。而实际上,过于频繁的提交也正是log file sync等待事件出现的主要原因。而log file parallel write等待事件则总是会出现的,只要LGWR开始刷新日志缓冲区,该进程就会等待log file parallel write等待事件,是不可避免的,不用过多关注该等待事件。同时,我们可以看到,由于过多的提交,导致生成的重做记录的数量非常巨大,redo size为19845884(20031044-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-01555:snapshot 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 Q Normal', '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等待事件,则比较常见的原因主要包括:
1) 联机日志文件放在一个很慢的磁盘上。可以用前面我们提到的log file parallel write的平均等待时间应该小于等于10个毫秒来判断,磁盘速度是否过慢。

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

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

    最好能够将每个联机日志文件都放在单独的小的、转速快的磁盘上,与数据文件所在的磁盘阵列分开。
如果可以的话,最好不要使用文件系统来管理磁盘,直接使用RAW设备,直接交给oracle来管理磁盘。而且,oracle中最适合放在RAW设备上的文件就是联机日志文件。


3.2.2 其他相关的等待事件
  当进程需要向日志缓冲区里拷贝重做记录时,发现没有足够的可用空间时,则必须等待log buffer space事件。如果一个进程花费了太多的时间在log buffer space等待事件上,这通常是由于下面两个原因:
1) 日志缓冲区尺寸太小。对于一个繁忙的批处理数据库系统里,一个太小的日志缓冲区(小于512K)会导致进程等待log buffer space。

2) LGWR进程太慢了,这也就是物理I/O速度过慢。可以通过检查log file parallel write等待事件的平均等待时间来判断,如果大于10毫秒,则说明LGWR过慢。
可以通过增加日志缓冲区的尺寸或加快LGWR的进程来减少log buffer space等待事件。

  而较小尺寸的联机日志文件会引起log file switch completion和log file switch (checkpoint incomplete)
这两个等待事件。log file switch completion表示进程正在等待日志切换完成。而当应用程序产生很多重做,然后由LGWR进程非常快得写日志文件并进行日志切换时,这时DBWR进程还没有把脏数据块写入磁盘以通知checkpoint结束时,这个时候产生log file switch (checkpoint incomplete)等待事件。可以通过增加联机日志文件的尺寸来减少log file switch completion和log file switch (checkpoint incomplete)。

使用如下SQL来显示有关日志缓冲区的latch free的misrate是否很大。 
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/3或1M时,就会触发LGWR进程。也就是说,oracle缺省认为LGWR进程在写日志缓冲区大小的1/3或1M的重做记录的过程中,剩下的日志缓冲区可以供新的重做记录的需要。当LGWR写完以后,那么这1/3或1M的日志缓冲区就又可以成为可用的日志块以容纳新的重做记录了。由此,我们可以很容易推导出,当我们设置日志缓冲区达到3M(3×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)

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

    通过使用NOLOGGING短语或者append提示来阻止重做记录的产生。该短语可以用在表空间上,表示该表空间里的对象缺省都不产生日志。也可以将表或者索引定义为NOLOGGING,这样该表上的DML操作都不生成重做记录,而对索引的重建也不生成重做记录。还可以直接用在以下的SQL语句中:
1) 直接路径装载(Direct Loader)以及直接路径插入(对insert使用append提示:insert /*+ append*/)。
2) create table table-name NOLOGGING as select …(CTAS)
3) alter table table-name NOLOGGING move tablespace tablespace-name
4) create index index-name NOLOGGING …
5) alter index index-name NOLOGGING rebuild…
6) 分区操作中,可以添加NOLOGGING短语。
7) 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进程在以下条件下触发:
1) 如果设置了自动归档模式,则日志切换时,由LGWR进程触发ARCH进程进行归档。这是最常见的方式。
2) 可以手工进行归档。使用命令:alter system archive log current表示启动ARCH进程,从而对当前的日志文件进行归档。
3) 如果ARCH进程在5分钟以后还没有接收到LGWR的通知,则发生超时,于是ARCH被唤醒以检查是否存在需要归档的日志文件。ARCH通过读取控制文件中的信息来决定是否需要归档以及应该归档哪些日志文件。但是在进行实例恢复或者介质恢复的过程中,ARCH进程不会启动。

  通常来说,ARCH进程启动以后的处理过程包括:
1) 从控制文件中读取未归档的日志文件。注意,可能会有多个日志文件。
2) 在内存中分配归档日志块,归档日志块的个数由隐藏参数_log_archive_buffer确定;每个归档日志块的大小则由_log_archive_buffer_size确定。
3) 打开联机归档日志组中的所有的日志文件,并校验它们的文件头。
4) 搜索可用的归档目的地。
5) 在可用的归档目的地创建并打开归档日志文件。
6) 在日志文件中从头到尾进行循环,取出每个日志块,并放入内存中的归档日志块,然后按照SCN的顺序放入归档日志文件中。
7) 关闭所有打开的文件,包括联机日志文件和归档日志文件。

  在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时,可以从以下几个方面入手。
1) 确定是否启用了自动归档,如果没有启用自动归档,则必须定期手工归档。
2) 需要确保归档日志文件不应该与联机归档日志文件放在同一个磁盘阵列里,而应该单独存放。同时避免把归档日志文件放在RAID-5设备上。
3) 把联机日志文件设置大一些,以便给ARCH进程的归档工作留出足够的时间。
4) 应该检查一下归档路径的设置,如果设置了很多的归档路径,则可以考虑减少一些归档路径。
5) 如果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为一个更大的值,等到批量写入完成以后,再将该参数设置回缺省值。 (全文完)
0
相关文章