4.2 buffer cache的统计信息
为了对buffer cache进行性能的诊断,oracle提供了很多有关buffer cache的统计信息。这些统计信息大致可以分成三类:1)有关用户发出的对内存数据块的请求相关的统计信息;2)有关DBWR后台进程对内存数据块处理相关的统计信息;3)RAC相关的统计信息。
我们在诊断buffer cache时,不需要关注所有的统计信息。这里主要介绍几个重要的统计信息,其他的统计信息都可以到《Oracle9i Database Reference: Appendix C》中找到。如下所示:
SQL> SELECT name, value FROM v$sysstat WHERE name in (
2 'session logical reads',
3 'physical reads',
4 'physical reads direct',
5 'physical reads direct (lob) ',
6 'consistent gets',
7 'db block gets',
8 'free buffer inspected')
9 /
NAME VALUE
---------------------------------------------------------------- ----------
session logical reads 73797
db block gets 498
consistent gets 73299
physical reads 29017
free buffer inspected 0
physical reads direct 40
这里做些简单的解释。
1. session logical reads:所有的逻辑读的数据块的数量。注意,其中包括先从硬盘上读数据块到内存里,再从内存里读数据块。
2. consistent gets:在一致性(consistent read)读模式下读取的内存里的数据块数量。包括从rollback segment里读取的数据块数量以及从data block buffer里读取的数据块数量。主要是通过select产生的。Update/delete也能产生很少量的此类数据块。注意:如果oracle的运行时间过长,由于oracle的bug导致consistent gets大大超过实际的数量。因此建议使用‘no work - consistent read gets’, ‘cleanouts only - consistent read gets’,‘rollbacks only - consistent read gets’, ‘cleanouts and rollbacks -consistent read gets’之和来代替consistent gets的值。
3. db block gets:在当前(current)模式下读取的内存里的数据块的数量。不是读取过去某个时点的数据块,而必须是当前最新的数据块。主要是通过update/delete/insert来产生的,因为DML需要当前最新的数据块才能对之进行改变。在字典管理表空间下,一些获得当前可用扩展空间的select语句也会产生此类数据块,因为必须得到当前最新的空间使用信息才能扩展。逻辑上,session logical reads = consistent gets + db block gets。
4. physical reads:从硬盘里读取的数据块的数量。注意,这个数量大于实际从硬盘里读取的数量,因为这部分block也包括了从操作系统缓存里读取的数据块数量。
5. physical reads direct:有些数据块不会先从硬盘读入内存再从内存读入PGA再传给用户,而是绕过SGA直接从硬盘读入PGA。比如并行查询以及从临时表空间读取数据。这部分数据块由于不缓存使得hit ratio不会被提高。
6. physical reads direct (lob):与physical reads direct一样。
7. free buffer inspected:这个值表示为了找到可用数据块而跳过的数据块的数量。这些被跳过的数据块就是脏的或被锁定的数据块。明显,这个值如果持续增长或很高,就需要增加buffer cache的大小了。
在获得了这些统计信息以后,我们可以计算buffer cache的命中率:
Hit Ratio = 1 –(physical reads – physical reads direct - physical reads direct (lob) ) / session logical reads
Miss ratio = (physical reads – physical reads direct -physical reads direct (lob) ) / session logical reads
通常在OLTP下,hit ratio应该高于0.9。否则如果低于0.9则需要增加buffer cache的大小。在考虑调整buffer cache hit ratio时,需要注意以下几点。
1. 如果上次增加buffer cache的大小以后,没有对提高hit ratio产生很大效果的话,不要盲目增加buffer cache的大小以提高性能。因为对于排序操作或并行读,oracle是绕过buffer cache进行的。
2. 在调整buffer cache时,尽量避免增加很多的内存而只是提高少量hit ratio的情况出现。
我们还可以查询每种buffer cache的统计信息,主要关注的还是consistent_gets和db_block_gets以及physical_reads的值。
SQL> SELECT name, block_size,physical_reads, db_block_gets,consistent_gets
2 FROM v$buffer_pool_statistics;
NAME BLOCK_SIZE PHYSICAL_READS DB_BLOCK_GETS CONSISTENT_GETS
-------------------- ---------- -------------- ------------- ---------------
DEFAULT 8192 28978 719 77591
DEFAULT 16384 2 80 11
v$sysstat中名称以DBWR开头的都是有关DBWR后台进程相关的统计信息。当DBWR进程写完脏数据块以后或者扫描完LRU链表以后更新这些统计信息。DBWR会基于被触发的频率以及所处理的内存数据块的数量与总内存数据块的数量的比例,来进行自我调整。我们可以通过这些统计信息得到一些对当前DBWR运行情况的认识。
4.3 buffer cache的等待事件
与buffer cache相关的等待事件包括:latch free、buffer busy waits、free buffer waits。曾经发生过的等待事件可以从v$system_event(一个等待事件对应一行记录)和v$session_event(一个session一个等待事件对应一行记录)中看到。而当前系统正在经历的等待事件可以从v$session_wait看到。
4.3.1 latch free等待
等待事件“latch free”中与buffer cache有关的有两类:cache buffers chains latch和cache buffers lru chainlatch。在理解了上面所描述的有关buffer cache的内部管理机制以后,就应该很容易理解这两个latch产生的原因。
对于buffer cache中的每个hash chain链表来说,都会有一个名为cache buffers chains latch的latch来保护对hash chain的并发操作,这种latch通常也叫作hash latch或CBC 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大概对应8个hash chain。
SQL> select x.ksppinm, y.ksppstvl, x.ksppdesc
2 from x$ksppi x , x$ksppcv y
3 where x.indx = y.indx
4 and x.ksppinm like '\_%' escape '\'
5 and ksppinm like '%_db_block_hash_buckets%'
6 ;
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 latch的latch free等待事件。
不够优化的SQL语句是导致cache buffers chains latch的主要原因。如果SQL语句需要访问过多的内存数据块,那么必然会持有latch很长时间。找出逻辑读特别大的sql语句进行调整。v$sqlarea里那些buffer_gets/executions为较大值的SQL语句就是那些需要调整的SQL语句。这种方式不是很有针对性,比较盲目。网上曾经有人提供了一个比较有针对性的、查找这种引起较为严重的cache buffers chains latch的SQL语句的方式,其原理是根据latch的地址,到x$bh中找对应的buffer header,x$bh的hladdr表示该buffer header所对应的latch地址。然后根据buffer header可以找到所对应的表的名称。最后可以到v$sqltext(也可以到stats$sqltext)中找到引用了这些表的SQL语句。我也列在这里。where条件中的rownum<10主要是为了不要返回太多的行,只要能够处理掉前10个latch等待就能有很大改观。
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_wait的p1raw字段来判断latch free等待事件是否是由于出现了热点块。如果p1raw保持一致,那么说明session在等待同一个latch地址,系统存在热点块。当然也可以通过x$bh的tch来判断是否出现了热点块,该值越高则数据块越热。
SQL> select sid, p1raw, p2, p3, seconds_in_wait, wait_time, state
2 from v$session_wait
3 where event = 'latch free'
4 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 chain的latch(也叫做lru latch)来管理。任何要访问working set的进程都必须先获得cache buffers lru chain latch。cache buffers lru chain latch争用也是由于低效的扫描过多的内存数据块的SQL语句引起的。调整这些语句以降低逻辑读和物理读。只要修改一下上面找引起cache buffers chains latch的SQL语句即可找到这样的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
/
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。
2. 如果没有足够的freelists,当同时对同一个表进行insert时,这就很容易引起buffer busy waits等待。如果正在等待buffer busy waits的session正在进行insert操作,那么需要检查以下那个表有多少freelists了。当然,由于freelists的不足主要会导致对于segment header的buffer busy waits等待。
3. 如果p3为220意味着有多个session同时修改在一个block(该block已经被读入内存了)里的不同的行。这种情况通常出现在高DML并发性的环境里。有三种方法可以降低该事件出现的频率:
a、 降低并发性。这个比较难实现。
b、 通过增加pctfree减少block里含有的行数。
c、 将该对象移到拥有较小block尺寸的表空间里(9i或以上)。
4. 如果数据块类型为data segment header(表或索引的segment header,不是undo segment header)上发生buffer busy waits等待事件,通常表明数据库里有些表或索引的段头具有频繁的活动。
5. 进程访问segment header主要有两种原因:一是获得或修改process freelists信息;二是扩展HWM。有三种方法可以降低该事件出现的频率:
a、 增加争用对象的freelists和freelist groups的数量。
b、 确定pctfree和pctused之间的间隔不要太小。
c、 确保next extent的尺寸不要太小。
6. d、9i以后,使用ASSM特性来管理block。
7. 如果数据块类型为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参数来减轻该等待。
8. 如果数据块类型为undo block,说明有多个session同时访问那些被更新过的block。这是应用系统的问题,在数据库来说对此无能为力。
4.3.3free buffer 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的大小。