Hash Bucket 和 Cache Buffer Chain
Oracle 把管理的所有的 Buffer 通过一个内部的 Hash 算法运算后存放到不同 Hash Bucket 中,这样通过 Hash Bucket 进行分割之后,众多的 Buffer被分布到一定数量的 Bucket之中,当用户需要在 Buffer中定位数据是否存在时,只需要通过同样的算法获得 Hash 值,然后到相应的 Bucket 中查找少量的 Buffer 即可确定。 每个 Buffer 的存放的 Bucket 由 Buffer 的数据块地址(
DBA,Data Block Address)运算决定。在 Bucket 内部,通过 Cache Buffer Chain(它是一个双向链表)将所有的 Buffer 通过
Buffer Header 信息联系起来。 Buffer Header 存放的是对应数据块的概要信息,包括数据块的文件号、块地址、状态等。在判断数据块在 Buffer 中是否存在,通过检查 Buffer header 即可确定。
ORACLE 将buffer cache中所有的buffer通过一个内部的Hash算法运算之后,将这些buffer放到不同的 Hash Bucket中。每一个Hash Bucket中都有一个Hash Chain List(也叫:cache buffers chain),通过这个list,将这个Bucket中的block串联起来。
对应每个Bucket,只存在一个Chain,当用户试图搜索Cache Buffer Chain时,必须首先获得Cache Buffer Chain Latch
Hash Chain List可以通过基表x$bh查看 ??
Buffer Header 数据可以从数据库的字典表X$BH中查询得到 ??
SQL> set linesize 50 SQL> desc x$bh Name Null? Type ----------------------- -------- ---------------- ADDR RAW(8) ---block在buffercache中的address INDX NUMBER INST_ID NUMBER CON_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地址 ...... TS# NUMBER FILE# NUMBER ...... TCH NUMBER --表征一个Buffer的访问次数,Buffer访问次数越多,说明该Buffer越“抢手”,也就越可能存在热点块竞争的问题 ......
查询可以获得当前数据库最繁忙的Buffer:
SQL> set linesize 200 SQL> select * from (select addr,ts#,file#,dbarfil,dbablk,tch from x$bh order by tch desc) where rownum<11; ADDR TS# FILE# DBARFIL DBABLK TCH ---------------- ---------- ---------- ---------- ---------- ---------- 00007F8DD59D2718 0 1 1 51224 255 00007F8DD59D2128 0 1 1 51217 255 00007F8DD59D2718 0 1 1 3917 255 00007F8DD59D2128 0 1 1 48233 255 00007F8DD59D5920 0 1 1 21927 255 00007F8DD59D57A0 0 1 1 3915 255 00007F8DD59D2128 0 1 1 20846 255 00007F8DD59D2128 0 1 1 13657 255 00007F8DD59D2718 0 1 1 51200 255 00007F8DD59D22A8 0 1 1 48234 255 10 rows selected.
查询得到这些热点Buffer都来自哪些对象:
SQL> col OWNER for A20 SQL> select e.owner,e.segment_name,e.segment_type from dba_extents e, (select * from (select addr,ts#,file#,dbarfil,dbablk,tch from x$bh order by tch desc) where rownum<11) b where e.relative_fno=b.dbarfil and e.block_id<=b.dbablk and e.block_id+ 2 3 e.blocks>b.dbablk; OWNER SEGMENT_NAME SEGMENT_TYPE -------------------- -------------------------------------------------------------------------------------------------------------------------------- ------------------ SYS C_FILE#_BLOCK# CLUSTER SYS C_FILE#_BLOCK# CLUSTER SYS C_FILE#_BLOCK# CLUSTER SYS C_FILE#_BLOCK# CLUSTER SYS C_FILE#_BLOCK# CLUSTER SYS C_FILE#_BLOCK# CLUSTER SYS C_FILE#_BLOCK# CLUSTER SYS C_FILE#_BLOCK# CLUSTER SYS C_FILE#_BLOCK# CLUSTER SYS C_FILE#_BLOCK# CLUSTER 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就串联起来了。



由于 Buffer 根据 Buffer Header 进行散列,最终决定存入那一个 Hash Bucket,那么 Hash Bucket 的数量在一定程度上决定了每个 Bucket 中 Buffer 数量的多少,也就间接影响了搜索的性能。所以在不同版本中,Oracle 一直在修改算法,优化 Hash Bucket 的数量。我们可以想象, Bucket 的数量多一些,那么在同一时间就可以有更多的同学可以拿到不同的抽屉,进行数据访问;但是更多的抽屉,显然需要更多的存放空间,更多的管理成本,所以优化在什么时候都不是简单的一元方程。
Latch:cache buffers chains的数量受隐含参数_db_block_hash_latches的影响,
Hash Bucket 的设置受一个隐含参数_ DB_BLOCK_HASH_BUCKETS 的影响。 从上图可看到 一个
latch:cache buffers chains(x$bh.hladdr) 可以保护多个
Hash Bucket,也就是说,如果我要访问某个block,我首先要获得这个latch,一个
Hash Bucket对应一个
Hash Chain List,而这个
Hash Chain List挂载了一个或者多个Buffer Header。
SQL> select BANNER_FULL from v$version; BANNER_FULL ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production Version 19.3.0.0.0
SQL> SELECT x.ksppinm NAME, y.ksppstvl VALUE, x.ksppdesc describ FROM x$ksppi x,x$ksppcv y WHERE x.inst_id =USERENV (‘Instance‘) AND y.inst_id =USERENV (‘Instance‘) AND x.indx = y.indx AND x.ksppinm LIKE‘%_db_block_hash%‘; NAME VALUE DESCRIB ----------------------------------------------------------------------- _db_block_hash_buckets 262144 Number of database block hash buckets _db_block_hash_latches 2048 Number of database block hash latches SQL> select count(distinct hladdr) from x$bh ; COUNT(DISTINCTHLADDR) --------------------- 2048 SQL> select count(*) from v$latch_children a,v$latchname b where a.latch#=b.latch# and b.name=‘cache buffers chains‘; COUNT(*) ---------- 2048
验证
SQL> select * from(select hladdr,count(*) from x$bh group by hladdr) where rownum<=5; HLADDR COUNT(*) ---------------- ---------- 00000000A80CE028 25 00000000A80CE0F0 31 00000000A80CE1B8 40 00000000A80CE280 18 00000000A80CE348 17 SQL> select hladdr,obj,dbarfil,dbablk,nxt_hash,prv_hash from x$bh where hladdr=‘00000000A80CE028‘ order by obj; HLADDR OBJ DBARFIL DBABLK NXT_HASH PRV_HASH ---------------- ---------- ---------- ---------- ---------------- ---------------- 00000000A80CE028 2 1 12226 00000000A80CEC68 00000000A80CEC68 00000000A80CE028 64 1 114311 00000000A80CE828 00000000A80CE828 00000000A80CE028 119 1 1280 00000000D4F9A130 00000000CEFC4598 00000000A80CE028 119 1 1280 00000000D1FC9E30 00000000A80CEBB8 00000000A80CE028 119 1 1280 00000000D8F84740 00000000D1FC9E30 00000000A80CE028 119 1 1280 00000000A80CEBB8 00000000D4F9A130 00000000A80CE028 378 1 25756 00000000A80CEA38 00000000A80CEA38 00000000A80CE028 385 1 21575 00000000A80CE878 00000000A80CE878 00000000A80CE028 568 1 3864 00000000D9F57B78 00000000D2FC53A8 00000000A80CE028 568 1 3864 00000000D5F995F0 00000000CFFC4700 00000000A80CE028 568 1 3864 00000000D2FC53A8 00000000A80CE8E8 00000000A80CE028 568 1 3864 00000000A80CE8E8 00000000D5F995F0 00000000A80CE028 720 1 115908 00000000A80CECC8 00000000A80CECC8 00000000A80CE028 780 1 14810 00000000E4FB9CD8 00000000A80CE998 00000000A80CE028 20324 1 47648 00000000D0FDF550 00000000A80CEB98 00000000A80CE028 20324 1 47648 00000000D3FAFB20 00000000CDFB50E8 00000000A80CE028 20324 1 47648 00000000D6FEF6A8 00000000D0FDF550 00000000A80CE028 20324 1 47648 00000000A80CEB98 00000000D3FAFB20 00000000A80CE028 73154 3 64962 00000000A80CEBC8 00000000A80CEBC8 00000000A80CE028 73610 3 78492 00000000A80CE998 00000000D6F864C8 00000000A80CE028 74505 3 173812 00000000A80CE678 00000000A80CE678 00000000A80CE028 74952 3 254615 00000000A80CECE8 00000000A80CECE8 00000000A80CE028 74978 3 253018 00000000A80CE848 00000000A80CE848 00000000A80CE028 75239 3 224361 00000000A80CE808 00000000A80CE808 00000000A80CE028 4294967295 4 1752 00000000A80CEAF8 00000000A80CEAF8 25 rows selected.
当一个用户进程想要访问Block时:
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开始
cache buffers chains latch可以只读共享,也就是说用户进程A以只读(select)的方式访问Block,这个时候获得了该latch,同时用户进程B也以
只读的方式访问Block不同的Block,但被同一个 cache buffers chains latch管理),那么这个时候由于是只读的访问,用户进程B也可以获得该latch。但是,如果用户进程B要以
独占的方式访问Block,那么用户进程B就会等待用户进程A释放该latch,这个时候Oracle就会对用户进程B标记一个
latch:cache buffers chains的等待事件。