4.3.2 buffer busy waits等待
当一个session在读取或修改buffer cache里的内存数据块时,首先必须获得cache buffers chains latch,获得以后,到hash chain上遍历直到找到需要的buffer header后。这时,该session必须在该buffer header上以share或exclusive模式(具体哪个模式由该session的操作决定)获得一个buffer lock或一个buffer pin。一旦buffer header被pin住,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列的值来共同诊断。如果p3为130意味着同时有很多session在访问同一个data block,而且该data block没有在内存里,而必须从磁盘上获取。有三种方法可以降低该事件出现的频率:
a、降低并发性。这个比较难实现。
b、找出并优化含有这些segment的SQL语句,以降低物理和逻辑读。
c、增加freelists和freelist groups。
如果没有足够的freelists,当同时对同一个表进行insert时,这就很容易引起buffer busy waits等待。如果正在等待buffer busy waits的session正在进行insert操作,那么需要检查以下那个表有多少freelists了。当然,由于freelists的不足主要会导致对于segment header的buffer busy waits等待。
如果p3为220意味着有多个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、增加争用对象的freelists和freelist groups的数量。
b、确定pctfree和pctused之间的间隔不要太小。
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。这是应用系统的问题,在数据库来说对此无能为力。
4.3.3 buffer busy waits等待
在一个数据块被读入buffer cache之前,oracle进程必须为该数据块获得一个对应的可用的内存数
据块。当session在LRU 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 waits的total_waits)。而v$sysstat里的free buffer inspected则说明oracle为了找到可用的内存数据块所所跳过的数据块的个数,如果buffer cache很空,有很多空的数据块的话,则该值为0。如果free buffer inspected相对free buffer requested来说很高,则说明oracle进程需要扫描更多的LRU链表上的数据块才可以找到可用的数据块。
SQL> select * 2 from v$sysstat 3 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秒。同时也可以看到,请求了290532493(free buffer requested)个可用的内存数据块,但是在这个过程中只跳过了2983596(free buffer inspected)个数据块,二者相差2个数量级。说明系统很容易就找到可用的内存数据块。
如果一个session花费了很多的时间等待free buffer waits等待事件的话,通常可能有以下原因:
1) 低效率的SQL语句:对于那些引起很大逻辑读的SQL语句(v$sql里的disk_reads),那些SQL语句可能进行了全表扫描,索引全扫描、或者通过了不正确的索引扫描表等。调整这些SQL语句以降低逻辑读。
2) DBWR进程不够多:也可以通过增加DBWR checkpoints的个数来降低free buffer waits。9i下,可以通过减小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的大小。