这两天抽空把Buffer Cache原理深入学习了下,感觉收获很多,整理了些资料大家一起分享下。
深入Buffer Cache之一——Buffer Cache概述
二、 Hash Bucket 与 Hash Chain List ( cache bufferschain )
Oracle将buffer cache中所有的buffer通过一个内部的Hash算法运算之后,将这些buffer放到不同的 Hash Bucket中。每一个 Hash Bucket中都有一个 Hash Chain List (也叫:cache buffers chain ),通过这个list,将这个Bucket中的block串联起来。
下面举个简单的例子来介绍一下Hash 算法,Oracle的Hash 算法肯定没这么简单,具体算法只有Oracle公司知道。
一个简单的mod函数 ,我们去mod 4
Ø 1mod 4 = 1
Ø 2mod 4 = 2
Ø 3mod 4 = 3
Ø 4mod 4 = 0
Ø 5mod 4 = 1
Ø 6mod 4 = 2
Ø 7mod 4 = 3
Ø 8mod 4 = 0
……………省略…………………..
那么这里就相当于创建了4个 Hash Bucket,如果有如下block:
blcok
BA(1,1) ------> (1+1)mod 4 =2
block
BA(1,2) ------> (1+2)mod 4 =3
block
BA(1,3) ------> (1+3)mod 4 =0
block
BA(1,4) ------> (1+4)mod 4 =1
block
BA(1,5) ------> (1+5)mod 5 =2
………........省略…………………....
比如我要访问block(1,5),那么我对它进行Hash运算,然后到Hash Bucket为2的这个Bucket里面去寻找,Hash Bucket 为2的这个Bucket 现在有2个block,这2个block是挂在 Hash Chain List上面的。
Hash Chain List到底是怎么组成的呢?这里我们就要提到x$bh这个基表了。
SQL>desc x$bh
Name Null? Type
------------------------ ----------------
ADDR RAW(8) ---block在buffercache中的address
INDX NUMBER
INST_ID NUMBER
HLADDR RAW(8) --(Hash Chain Latch Address)latch:cache buffers chains 的地址
BLSIZ NUMBER
NXT_HASH RAW(8) ---指向同一个Hash Chain List的下一个block地址
PRV_HASH RAW(8) ---指向同一个HashChain List的上一个block地址
NXT_REPL RAW(8) ---指向LRU list中的下一个block地址
PRV_REPL RAW(8) ---指向LRUlist中的上一个block地址
TCH NUMBER --表征一个Buffer的访问次数,Buffer访问次数越多,说明该Buffer越“抢手”,也就越可能存在热点块竞争的问题
………………省略…………………………
通过以下查询可以获得当前数据库最繁忙的Buffer:
SQL> select *
2 from (selectaddr,ts#,file#,dbarfil,dbablk,tch
3 from x$bh
4 order by tch desc)
5 where rownum<11;
ADDR TS# FILE# DBARFIL DBABLK TCH
-------- ---------- ---------- ---------- ---------- ----------
0D2C9F74 0 1 1 1658 26393
0D2C9F74 0 1 1 1674 21681
0D2C9F74 0 1 1 92 6237
0D2C9F74 0 1 1 796 5853
0D2C9F74 0 1 1 797 5852
0D2C9F74 0 1 1 1666 5711
0D2C9F74 2 3 3 23632 5447
0D2C9F74 2 3 3 23627 5425
0D2C9F74 2 3 3 23629 5423
0D2C9F74 2 3 3 23631 5423
10 rows selected
再结合DBA_EXTENTS中的信息,可以查询得到这些热点Buffer都来自哪些对象:
SQL> select e.owner,e.segment_name,e.segment_type
2 from dba_extents e,
3 (select *
4 from (selectaddr,ts#,file#,dbarfil,dbablk,tch
5 from x$bh
6 order by tch desc)
7 where rownum<11) b
8 where e.relative_fno=b.dbarfil
9 and e.block_id<=b.dbablk
10 and e.block_id+e.blocks>b.dbablk;
OWNER SEGMENT_NAME SEGMENT_TYPE
-------------- --------------------------- ------------------
SYS JOB$ TABLE
SYSMAN MGMT_JOB_EMD_STATUS_QUEUE TABLE
SYSMAN MGMT_JOB_EMD_STATUS_QUEUE TABLE
SYSMAN MGMT_JOB_EMD_STATUS_QUEUE TABLE
SYSMAN MGMT_JOB_EMD_STATUS_QUEUE TABLE
SYSMAN MGMT_JOB_EMD_STATUS_QUEUE TABLE
SYSMAN MGMT_JOB_EMD_STATUS_QUEUE TABLE
SYSMAN MGMT_METRIC_DEPENDENCY TABLE
SYS I_JOB_NEXT INDEX
SYS _SYSSMU9$ TYPE2 UNDO
10 rows selected
每个Buffer在x$bh中都存在一条记录, Hash Chain List就是由x$bh中的NXT_HASH,PRV_HASH 这2个指针构成了一个双向链表,其示意图如下:
通过NXT_HASH,PRV_HASH这2个指针,那么在同一个 Hash Chain List的block就串联起来了。
理解了 Hash Bucket 和 Hash Chain List,我们现在来看看 Hash Bucket 与 Hash Chain List管理Buffer Cache 的结构示意图:
从图中我们可以看到,一个 latch:cache buffers chains(x$bh.hladdr) 可以保护多个 Hash Bucket,也就是说,如果我要访问某个block,我首先要获得这个latch,一个 Hash Bucket对应一个 Hash Chain List,而这个 Hash Chain List挂载了一个或者多个Buffer Header。
HashBucket的数量受隐含参数_db_block_hash_buckets的影响,Latch:cache buffers chains的数量受隐含参数_db_block_hash_latches的影响,该隐含参数可以通过如下查询查看:
SQL> select * from v$version;
BANNER
------------------------------------------------------------------
OracleDatabase 10g Enterprise Edition Release 10.2.0.3.0 - 64 bi
SQL> SELECT x.ksppinm NAME, y.ksppstvlVALUE, x.ksppdesc describ
2 FROM x$ksppi x,x$ksppcv y
3 WHERE x.inst_id =USERENV ('Instance')
4 AND y.inst_id =USERENV ('Instance')
5 AND x.indx = y.indx
6 AND x.ksppinm LIKE'%_db_block_hash%'
7 /
NAME VALUE DESCRIB
------------------------- -----------------------------------------------------
_db_block_hash_buckets 524288 Numberof database block hash buckets
_db_block_hash_latches 16384 Numberof database block hash latches
_db_block_hash_buckets 该隐含参数在8i以前 等于db_block_buffers/4的下一个素数 到了8i 该参数等于 db_block_buffers*2 ,到了9i 以后,该参数取的是小于且最接近 db_block_buffers*2 的一个素数。
_db_block_hash_latches 该隐含参数表示的是 cache buffers chains latch的数量,它怎么计算的我们不用深究。
可以看到,从8i以后 Hash Bucket数量比以前提升了8倍。可以用下面查询计算 cache buffers chains latch的数量:
方法一:
SQL>select count(*)
2 from v$latch_children a,v$latchname b
3 wherea.latch#=b.latch# and b.name='cache buffers chains';
COUNT(*)
----------
16384
方法二:
SQL>select count(distinct hladdr) from x$bh ;
COUNT(DISTINCTHLADDR)
---------------------
16384
根据我们的查询,那么一个 cache buffers chains latch 平均下来要管理32个 Hash Bucket,那么现在我们随意的找一个latch,来验证一下前面提到的结构图:
SQL>select *
2 from(select hladdr,count(*) from x$bh group by hladdr)
3 whererownum<=5;
HLADDR COUNT(*)
----------------------------------------
C000000469F08828 15
C000000469F088F0 14
C000000469F089B8 15
C000000469F08A80 24
C000000469F08B48 17
我们查询latch address 为C000000469F08828 所保护的data block:
SQL>select hladdr,obj,dbarfil,dbablk,nxt_hash,prv_hash
2 fromx$bh
3 wherehladdr='C000000469F08828'
4 orderby obj;
HLADDR OBJ DBARFIL DBABLK NXT_HASH PRV_HASH
----------------------------------------- ------------ ---------- -----------------------------------------------------------
C000000469F08828 2 388 322034 C0000004686ECBD0 C00000017EF8D658
C000000469F08828 2 388 396246 C0000004686ECA60 C0000004686ECA60
C000000469F08828 18 411 674831 C0000004686ECC00 C0000004686ECC00
C000000469F08828 216 411 438948 C0000004686ECBB0 C0000004686ECBB0
C000000469F08828 216 220 100217 C0000004686ECAA0 C0000004686ECAA0
C000000469F08828 216 220 60942 C000000151FB5DD8 C0000004686ECBD0
C000000469F08828 569 411 67655 C00000011FF81668 C0000001E8FB7AC0
C000000469F08828 569 280 1294 C0000004686ECB60 C000000177F9F078
C000000469F08828 58744570 210 332639 C000000177F9F078 C0000004686ECB60
C000000469F08828 65178270 254 408901 C0000004686ECBF0 C0000004686ECBF0
C000000469F08828 65347592 84 615093 C0000004686ECB90 C0000004686ECB90
C000000469F08828 65349200 765 1259399 C0000004686ECA70 C0000004686ECA70
请观察DBA(388,396246),它的NXT_HASH与PRV_HASH相同,也就是说DBA(388,396246)挂载在只包含有1个data block的 Hash Chain上。
另外也请注意,我通过count(*)计算出来的时候有15个block,但是查询之后就变成了12个block,那说明有3个block被刷到磁盘上了。
当一个用户进程想要访问Block(569,411)时:
1> 对该 Block 运用 Hash 算法,得到 Hash 值。
2> 获得 cache buffers chains latch
3> 到相应的 Hash Bucket 中搜寻相应 Buffer Header
4> 如果找到相应的 Buffer Header ,然后判断该 Buffer 的状态,看是否需要构造 CR Block (一致性读块),或者 Buffer 处于 pin 的状态,最后读取。
5> 如果找不到,就从磁盘读入到 Buffer Cache 中。
在Oracle9i以前,如果其它用户进程已经获得了这个latch,那么新的进程就必须等待,直到该用户进程搜索完毕(搜索完毕之后就会释放该latch)。
从Oracle9i开始 cache buffers chains latch可以只读共享,也就是说用户进程A以只读(select)的方式访问Block(84,615093),这个时候获得了该latch,同时用户进程B也以 只读的方式访问Block(765,1259399)(不同的Block,但被同一个 cache buffers chains latch管理),那么这个时候由于是只读的访问,用户进程B也可以获得该latch。但是,如果用户进程B要以 独占的方式访问Block(765,1259399),那么用户进程B就会等待用户进程A释放该latch,这个时候Oracle就会对用户进程B标记一个 latch:cache buffers chains的等待事件。