buffer cache的等待事件

  与buffer cache相关的等待事件包括:latch free、buffer busy waits、free buffer waits

1.latch free等待
      
等待事件“latch free”中与buffer cache有关的有两类:cache buffers chains latch和cache buffers lru chain latch。在理解了上面所描述的有关buffer cache的内部管理机制以后,就应该很容易理解这两个latch产生的原因。
       对于buffer cache中的每个hash chain链表来说,都会有一个名为cache buffers chains latch的latch来保护对hash chain的并发操作,这种latch通常也叫作hash latchCBC latch

数据库中会有很多的cache buffers chains latch,每个latch都叫做child cache buffers chains latch。一个child cache buffers chains latch会管理多个hash chain。前面我们知道,hash chain的数量由一个隐藏参数:_db_block_hash_buckets决定。同样也有一个隐藏参数:_db_block_hash_latches来决定有多少个cache buffers chains latch来管理这些hash chain。该参数的缺省值由buffer cache中所含有的内存数据块的多少决定,当内存数据块的数量
     •少于2052个时,_db_block_hash_latches = power(2,trunc(log(2,内存块数量 - 4) - 1))
   
•多于131075个时,_db_block_hash_latches = power(2,trunc(log(2, db_block_buffers - 4) - 6))
   
•位于2052与131075 buffers之间,
_db_block_hash_latches = 1024
可以使用下面的SQL语句来确定当前系统的cache buffers chains latch的数量。

SQL> select count(distinct(hladdr)) from x$bh;
COUNT(DISTINCT(HLADDR))
-----------------------
                  1024
SQL> select count(*) from v$latch_children where name='cache buffers chains';
  COUNT(*)

----------
      1024

       在知道了cache buffers chains latch的数量以后,我们只需要用hash chain的数量除以latch的数量以后,就可以算出每个latch管理多少个hash chain了。我们将下面7532除以1024,就可以知道,当前的系统中,每个latch大概对应8hash chain

SQL> select x.ksppinm, y.ksppstvl, x.ksppdesc  from x$ksppi x , x$ksppcv y
where x.indx = y.indx
and x.ksppinm like '\_%' escape '\'
and ksppinm like '%_db_block_hash_buckets%';
KSPPINM                  KSPPSTVL KSPPDESC
---------------------- -------- ----------------
_db_block_hash_buckets 7523     Number of database block hash buckets

      当数据库在hash chain搜索需要的数据块时,必须先获得cache buffers chains latch。然后在扫描hash chain的过程中会一直持有该latch,直到找到所要的数据块才会释放该latch。当有进程一直在扫描某条hash chain,而其他进程也要扫描相同的hash chain时,其他进程就必须等待类型为cache buffers chains latchlatch free等待事件。

      不够优化SQL语句是导致cache buffers chains latch的主要原因。如果SQL语句需要访问过多的内存数据块,那么必然会持有latch很长时间。找出逻辑读特别大的sql语句进行调整。v$sqlarea里那些buffer_gets/executions为较大值的SQL语句就是那些需要调整的SQL语句。这种方式不是很有针对性,比较盲目。

        网上曾经有人提供了一个比较有针对性的、查找这种引起较为严重的cache buffers chains latchSQL语句的方式,其原理是根据latch的地址,到x$bh中找对应的buffer headerx$bhhladdr表示该buffer header所对应的latch地址。然后根据buffer header可以找到所对应的表的名称。最后可以到v$sqltext(也可以到stats$sqltext)中找到引用了这些表的SQL语句。我也列在这里。where条件中的rownum<10主要是为了不要返回太多的行,只要能够处理掉前10latch等待就能有很大改观。

select /**//*+ rule */ s.sql_text
from x$bh a,dba_extents b,
(select * from (select addr from v$latch_children
    where name = 'cache buffers chains' order by sleeps desc)
where rownum<11) c,
v$sqltext s
where a.hladdr = c.addr
  and a.dbarfil = b.relative_fno
  and a.dbablk between b.block_id and b.block_id + b.blocks
  and s.sql_text like '%'||b.segment_name||'%' and b.segment_type='TABLE'
order by s.hash_value,s.address,s.piece
/

       还有一个原因可能会引起cache buffers chains latch,就是热点数据块问题。这是指多个session重复访问一个或多个被同一个child cache buffers chains latch保护的内存数据块。这主要是应用程序的问题。大多数情况下,单纯增加child cache buffers chains latches的个数对提高性能没有作用。这是因为内存数据块是根据数据块地址以及hash chain的个数来进行hash运算从而得到具体的hash chain的,而不是根据child cache buffers chains latches的个数。如果数据块的地址以及hash chain的个数保持一致,那么热点块仍然很有可能会被hash到同一个child cache buffers chains latch上。可以通过v$session_waitp1raw字段来判断latch free等待事件是否是由于出现了热点块。如果p1raw保持一致,那么说明session在等待同一个latch地址,系统存在热点块。当然也可以通过x$bhtch来判断是否出现了热点块,该值越高则数据块越热。

SQL> select sid, p1raw, p2, p3, seconds_in_wait, wait_time, state   from   v$session_wait   where  event = 'latch free'
order by p2, p1raw;
 SID P1RAW     P2         P3 SECONDS_IN_WAIT  WAIT_TIME STATE
---- -------- --- --- --------------- ----------  
  38 6666535C 13   1               1          2 WAITED KNOWN TIME
  42 6666535C 13   1               1          2 WAITED KNOWN TIME
  44 6666535C 13   3               1          4 WAITED KNOWN TIME
………………………
  85 6666535C 13   3               1         12 WAITED KNOWN TIME
 214 6666535C 138   1               1          2 WAITED KNOWN TIME

      接下来,我们就可以根据p1raw的值去找到所对应的内存数据块以及对应的表的名称了。

select a.hladdr, a.file#, a.dbablk, a.tch, a.obj, b.object_name
from   x$bh a, dba_objects b
where  (a.obj = b.object_id  or  a.obj = b.data_object_id)
and    a.hladdr = '6666535C';

      要解决热点块的问题,可以通过将热点块中的行分散到多个数据块中去,这样原来的热点块就变成了多个数据块,这样被hash到同一个latch的几率就降低了。如果热点块属于表,则可以先将表的数据导出来,然后增加表的pctfree值,最后将数据再导入。如果热点块属于索引,则可以设定较高的 pctfree参数后,重建索引。注意,这会增加索引的高度。
      
通过前面我们已经知道,每个working set都会有一个名为cache buffers lru chainlatch(也叫做lru latch)来管理。任何要访问working set的进程都必须先获得cache buffers lru chain latchcache buffers lru chain latch争用也是由于低效的扫描过多的内存数据块的SQL语句引起的。调整这些语句以降低逻辑读和物理读。只要修改一下上面找引起cache buffers chains latchSQL语句即可找到这样的SQL语句。

select /**//*+ rule */ s.sql_text
from x$bh a,dba_extents b,
(select * from (select addr from v$latch_children
    where name = 'cache buffers lru chain' order by sleeps desc)
where rownum<11) c,
v$sqltext s
where a.hladdr = c.addr
  and a.dbarfil = b.relative_fno
  and a.dbablk between b.block_id and b.block_id + b.blocks
  and s.sql_text like '%'||b.segment_name||'%' and b.segment_type='TABLE'
order by s.hash_value,s.address,s.piece
/

2.  buffer busy waits等待
       
当一个session在读取或修改buffer cache里的内存数据块时,首先必须获得cache buffers chains latch,获得以后,到hash chain上遍历直到找到需要的buffer header后。这时,该session必须在该buffer header上以shareexclusive模式(具体哪个模式由该session的操作决定)获得一个buffer lock或一个buffer pin。一旦buffer headerpin住,session就将释放cache buffers chains latch,然后可以在该buffer上进行操作了。如果无法获得buffer pin,那么该session就会等待buffer busy waits等待事件。该等待事件不会出现在session的私有PGA里。
        buffer busy waits
等待事件不能像latch free等待那样可以相对比较容易的进行事后跟踪。对于该等待事件,oracle提供了v$waitstat视图。v$waitstat里的记录都是buffer busy waits等待事件发生时进行更新的。也就是说,该视图体现的都是buffer busy waits等待事件的统计数据。但这只能给你提供一个大概的buffer busy waits的分布。如果要想具体的诊断该等待事件,只能当发生该等待时,到v$session_wait里去找原因,从而才能找到解决的办法。处理buffer busy wait等待事件时,首先使用下面的SQL语句找到发生等待的数据块类别以及对应的segment

select 'Segment Header' class,a.segment_type, a.segment_name, a.partition_name
from   dba_segments a, v$session_wait b
where  a.header_file  = b.p1
and    a.header_block = b.p2
and    b.event        = 'buffer busy waits'
union
select 'Freelist Groups' class,
        a.segment_type, a.segment_name, a.partition_name
from   dba_segments a, v$session_wait b
where  b.p2 between a.header_block + 1 and (a.header_block + a.freelist_groups)
and    a.header_file     = b.p1
and    a.freelist_groups > 1
and    b.event           = 'buffer busy waits'
union
select a.segment_type || ' block' class,a.segment_type, a.segment_name, a.partition_name
from   dba_extents a, v$session_wait b
where  b.p2 between a.block_id and a.block_id + a.blocks - 1
and    a.file_id  = b.p1
and    b.event    = 'buffer busy waits'
and    not exists (select 1
                  from   dba_segments
                  where  header_file  = b.p1
                  and    header_block = b.p2);

   然后,根据不同的数据块类型进行相应的处理。
     1) 
如果数据块类型为data block,如果版本为10g之前,则可以同时参照p3列的值来共同诊断。如果p3130意味着同时有很多session在访问同一个data block,而且该data block没有在内存里,而必须从磁盘上获取。有三种方法可以降低该事件出现的频率:
     a
、降低并发性。这个比较难实现。
     
b、找出并优化含有这些segmentSQL语句,以降低物理和逻辑读。
     
c、增加freelistsfreelist groups
如果没有足够的freelists,当同时对同一个表进行insert时,这就很容易引起buffer busy waits等待。如果正在等待buffer busy waitssession正在进行insert操作,那么需要检查以下那个表有多少freelists了。当然,由于freelists的不足主要会导致对于segment headerbuffer busy waits等待。
   
如果p3220意味着有多个session同时修改在一个block(block已经被读入内存了)里的不同的行。这种情况通常出现在高DML并发性的环境里。有三种方法可以降低该事件出现的频率:
    a
、降低并发性。这个比较难实现。
    b
、通过增加pctfree减少block里含有的行数。
    c
、将该对象移到拥有较小block尺寸的表空间里(9i或以上)
   2) 
如果数据块类型为data segment header(表或索引的segment header,不是undo segment header)上发生buffer busy waits等待事件,通常表明数据库里有些表或索引的段头具有频繁的活动。
进程访问segment header主要有两种原因:一是获得或修改process freelists信息;二是扩展HWM。有三种方法可以降低该事件出现的频率:
    a
、增加争用对象的freelistsfreelist groups的数量。
    b
、确定pctfreepctused之间的间隔不要太小。
    c
、确保next extent的尺寸不要太小。
    d
9i以后,使用ASSM特性来管理block
   3) 
如果数据块类型为undo segment headers的争用等待,表明数据库中的rollback segments太少,或者他们的extent size太小,导致对于同一个segment header的大量更新。如果使用了9i以后的auto undo management,则不用处理,因为oracle会根据需要自动创建新的undo segments。如果是9i之前,则可以创建新的private rollback segments,并把它们online,或者通过降低transactions_per_rollback_segment参数来减轻该等待。
    4) 
如果数据块类型为undo block,说明有多个session同时访问那些被更新过的block。这是应用系统的问题,在数据库来说对此无能为力。
3. free buffer waits等待
     
在一个数据块被读入buffer cache之前,oracle进程必须为该数据块获得一个对应的可用的内存数据块。当sessionLRU list上无法发现一个可用的内存数据块或者搜寻可用的内存数据块被暂停的时候,该session就必须等待free buffer waits事件。

     
从前面的描述,我们已经知道,一个需要可用内存数据块的前台进程会连续扫描LRU链表,直到达到一个限定值(也就是隐藏参数_db_block_max_scan_pct所指定的值,表示已经扫描的buffer header数量占整个LRU链表上的buffer header的总数量,在9i中该限定值为40%)。如果到该限定值时还没找到可用内存数据块时,该前台进程就会触发DBWR进程以便清空一些脏数据块,从而使得在辅助LRU链表上能够挂上一些可用的内存数据块。在DBWR进程工作时,该前台进程就必须等待free buffer waits
     
oracle跟踪每次对于可用的内存数据块的请求次数(记录在v$sysstat里的free buffer requested),也跟踪每次请求可用的内存数据块失败的次数(记录在v$system_event里的free buffer waitstotal_waits)。而v$sysstat里的free buffer inspected则说明oracle为了找到可用的内存数据块所所跳过的数据块的个数,如果buffer cache很空,有很多空的数据块的话,则该值为0。如果free buffer inspected相对free buffer requested来说很高,则说明oracle进程需要扫描更多的LRU链表上的数据块才可以找到可用的数据块。

SQL> select * from   v$sysstat
where  name in ('free buffer requested', 'free buffer inspected');
STATISTIC# NAME                                  CLASS      VALUE
---------- ------------------------------  ----------- ----------
        75 free buffer requested                 8            290532493
        79 free buffer inspected                 8               2983596
SQL> select *
  2  from   v$system_event
  3  where event = 'free buffer waits';
EVENT            TOTAL_WAITS TOTAL_TIMEOUTS TIME_WAITED AVERAGE_WAIT TIME_WAITED_MICRO
----------------- ----------- -------------- ----------- ------------ -----------------
free buffer waits       1003            476       71075           71         710749256

     可以看到,该系统的free buffer waits等待很少,总共等待的时间才0.476秒。同时也可以看到,请求了290532493free buffer requested)个可用的内存数据块,但是在这个过程中只跳过了2983596free buffer inspected)个数据块,二者相差2个数量级。说明系统很容易就找到可用的内存数据块。
     
如果一个session花费了很多的时间等待free buffer waits等待事件的话,通常可能有以下原因:
     
1) 低效率的SQL语句:对于那些引起很大逻辑读的SQL语句(v$sql里的disk_reads),那些SQL语句可能进行了全表扫描,索引全扫描、或者通过了不正确的索引扫描表等。调整这些SQL语句以降低逻辑读。
     
2) DBWR进程不够多:也可以通过增加DBWR checkpoints的个数来降低free buffer waits9i下,可以通过减小fast_start_mttr_target参数来缩短MTTR,从而增加DBWR进程启动的次数。然而,这也有可能引起进程等待write complete waits事件。
     
3) I/O子系统太慢。
     
4) 延迟的块清除(block clearouts):通常发生的情形是,晚上向数据库导入了一个很大的表。然后早上运行应用系统时,会发现有有进程在等待buffer busy waits。这是因为第一个访问该表的进程将进行一个延迟的块清除,而这会导致free buffer waits等待事件。解决方法是在导入表完毕以后,执行一句全表扫描,比如通常是:select count(*) from该大表。这样在后面的进程再次访问的时候就不会产生free buffer waits等待事件了。
    5) buffer cache
太小:遇到free buffer waits事件,首先想到的就是增加buffer cache的大小。

你可能感兴趣的:(等待事件)