1、HASH链是ORACLE为了提高数据块定位速度而设计的。将db cache中正在使用的数据块都放到HASH链上,这个HASH链是多个BUCKET组成的多链结构,每个BUCKET就是一条链的链头,从而引出一条独立的双向链。ORACLE设计了一个散列算法,通过数据文件的文件号和数据块的块号,进行散列运算,得到的散列值就是这个数据块所在的BUCKET号码。得到BUCKET位置后就可以很快地找到链头,从链头的双向链表扫描下去,即可以找到相关的数据块。Bucket数量是固定的一旦数据库启动后就不会改变,BUCKET数量由隐含参数 _DB_BLOCK_HASH_BUCKET确定。8i的默认值是2*DB_BLOCK_BUFFERS,9i的默认值是大于两倍的DB Block Buffer数量的最小的2的幂次的数值。虽然ORACLE的每个版本对该参数的定义不同,但是有一点是不变的,就是数据库缓冲区的Hash Chains的BUCKET的数量始终大于DB Buffer数量的两倍。虽然我们无法获得ORACLE内部散列算法的详细细节,但是有一点可以肯定的,ORACLE设计HASH链的基本思路是,每个链上有最少的BUFFER数量,最佳的情况是每条链上只有一个BUFFER.
2、访问HASH链时,为了保证数据访问的一致性,通过cache buffers chains闩锁来保护Hash链的数据结构。因此如果出现cache bufers chains闩锁争用,那么一般来说都和HASH链的访问有关。如果碰到该闩锁等待严重,首先应该检查是否存在逻辑读(buffer get)十分高的SQL,看看这些SQL的执行计划是否存在问题,是否对大表做了全表扫描。此外较严重的热块冲突也会加大cache buffers chains闩锁的争用,这一点需要注意。
二、HASH链示意图
BUCKET是存放在共享池中的,CBC是存放在db cache 中的。所以,当增大db cache时应考虑是否需要增大shared pool。相同的文件号块号(即相同DBA)通过散列算法会分布在一个BUCKET上,所以CR读的块肯定和当前值块在一个CBC链上。
三、做实验查看HASH链
1、创建测试表(普通用户下)
create table panda_t2(id int,name varchar2(100));
begin
for i in 1 .. 90000
loop
insert into panda_t2 values(i,'panda'||i);
end loop;
commit;
end;
/
2、表中数据对于的文件号和块号(普通用户下)
SQL> select dbms_rowid.rowid_relative_fno(rowid) file#,dbms_rowid.rowid_block_number(rowid) block#,id,name from panda_t2 where rownum<50;
FILE# BLOCK# ID NAME
---------- ---------- ---------- -----------------------------------------------------
4 171 460 panda460
4 171 461 panda461
4 171 462 panda462
4 171 463 panda463
4 171 464 panda464
注:
4 ,171 ===DBA (DATA BLOCK ADDRESS)
rdba(relative data block address)相对地址
3、查询4号文件171号块对应的bh地址(DBA用户下操作)
SQL> select lower(ba) from x$bh where FILE#=4 and DBABLK=171;
LOWER(BA)
--------
407a6000
注:
rowid 6+3+6+3(对象号+文件号+数据块号+记录槽号) rbms_rowid的包在官方文档的pl/sql里有说明
rdba(relative data block address)相对地址
bh中含有dba(数据在磁盘上的物理地址)与ba(数据在内存上的buffer地址)的映射关系
4、把整个buffer的BH转储出来
SQL> alter session set events 'immediate trace name buffers level 1';
Session altered.
得到转储文件(Default trace file)
SQL> select * from v$diag_info where name = 'Default Trace File';
INST_ID NAME VALUE
---------- -------------------- -----------------------------------------------------------
1 Default Trace File/u01/app/oracle/diag/rdbms/panda/panda/trace/panda_ora_2829.trc
这里贴出一个BH作为示例:
BH (0x407fa1f4) file#: 4 rdba: 0x010000ab (4/171) class: 1 ba: 0x407a6000
set: 3 pool 3 bsz: 8192 bsi: 0 sflg: 2 pwc: 116,19
dbwrid: 0 obj: 73419 objn: 73419 tsn: 4 afn: 4 hint: f
hash: [0x4e381510,0x4e381510] lru: [0x407fa374,0x407fa1cc]
ckptq: [NULL] fileq: [NULL] objq: [0x407fa38c,0x407fa1e4]
st: XCURRENT md: NULL tch: 2
flags: only_sequential_access
LRBA: [0x0.0.0] LSCN: [0x0.0] HSCN: [0xffff.ffffffff] HSUB: [65535]
cr pin refcnt: 0 sh pin refcnt: 0
注:
rdba: 0x010000ab==>4号文件171号块(转换为二进制,前十位代表文件号,后22位代表块号。所以为4号文件171号块)
0x010000ab==>0000 0001 00(前十位)==>4
后22位:
SQL> select to_number('ab','xxxxxxxxxxxxx') from dual;
TO_NUMBER('C3','XXXXXXXXXXXXX')
-------------------------------
171
hash: [0x4e381510,0x4e381510]:上面贴出来的这个,发现里面的地址是一样的,这个很有意思。一样的原因是这个bh所在的hash链只有一个bh。
0x4e381510这个地址都指向了BUCKET(BUCKET是存放在shared pool里的)
5、查询出4号文件171号块的BA,NXT_HASH,PRV_HASH和BH的地址范围(在DBA用户下执行)
SQL> select lower(ba) ,lower(NXT_HASH),lower(PRV_HASH) from x$bh where FILE#=4 and DBABLK=171
LOWER(BA LOWER(NX LOWER(PR
-------- -------- --------
407a6000 4e381510 4e381510
SQL> select lower(min(ba)),lower(max(ba)) from x$bh;
LOWER(MI LOWER(MA
-------- --------
3f0f2000 44be0000
可以看到4e381510是不在BH地址范围的,说明4e381510指的不是BH。从一个方面验证了当hash: [0x4e381510,0x4e381510]的两个地址相同时,说明这个BUCKET只挂了一个BH,这个BH的NXT_HASH和PRV_HASH指向它的BUCKET;
6、找一个Cache Buffer Chain(CBC)多于一个BH的链(即找一个hash[]中地址不一样的BH所在的CBC)
查看了刚才dump出来的/u01/app/oracle/diag/rdbms/panda/panda/trace/panda_ora_2829.trc文件,找了半天没找到有hash[]不一样BH。
这验证了前面的一句话 “ORACLE设计HASH链的基本思路是,每个链上有最少的BUFFER数量,最佳的情况是每条链上只有一个BUFFER”。
为了得到一条多BH的CBC链,我修改一条数据(因为修改前后的数据的文件号和块号是一样的,所以他们的BH会在一个CBC上)
SQL> update panda_t2 set name = 'panda_update' where id=464;
1 row updated.
SQL> commit;
Commit complete.
SQL> select lower(ba) from x$bh where FILE#=4 and DBABLK=171;
#这个查询结果有两条是因为有个CR块
LOWER(BA
--------
3e980000
407a6000
SQL> select lower(ba) ,lower(NXT_HASH),lower(PRV_HASH) from x$bh where FILE#=4 and DBABLK=171;
#这个查询结果有两条是因为有个CR块
LOWER(BA LOWER(NX LOWER(PR
-------- -------- --------
3e980000 407fa270 4e381510
407a6000 4e381510 3ebebeb4
SQL> select lower(min(ba)),lower(max(ba)) from x$bh;
#这个的lower(min(ba))和上面的不一样是应为BH增加了,所以BH的ba范围变大了
LOWER(MI LOWER(MA
-------- --------
3e6d6000 44be0000
SQL> alter session set events 'immediate trace name buffers level 1';
Session altered.
SQL> select * from v$diag_info where name = 'Default Trace File';
INST_ID NAME VALUE
---------- ------------------ ---------------------------------------------------------------
1 Default Trace File /u01/app/oracle/diag/rdbms/panda/panda/trace/panda_ora_2829.trc
#这个是修改后的值,CLASS:1 代表数据块,st: XCURRENT 这个代表当前值
BH (0x3ebebe38) file#: 4 rdba: 0x010000ab (4/171) class: 1 ba: 0x3e980000
set: 3 pool 3 bsz: 8192 bsi: 0 sflg: 2 pwc: 250,28
dbwrid: 0 obj: 73419 objn: 73419 tsn: 4 afn: 4 hint: f
hash: [0x407fa270,0x4e381510] lru: [0x3ebebfb8,0x3ebebe10]
obj-flags: object_ckpt_list
ckptq: [0x44bfab10,0x3ebec25c] fileq: [0x4dc629f8,0x4dc629f8] objq: [0x4a479a34,0x4a479a34]
st: XCURRENT md: NULL tch: 2
flags: buffer_dirty redo_since_read
LRBA: [0x9.142e.0] LSCN: [0x0.d7253] HSCN: [0x0.d7253] HSUB: [1]
#这个是修前的值,CLASS:1 代表数据块,st: CR 这个代表CR块(为了满足数据库的一致性,为CR读提供的)
BH (0x407fa1f4) file#: 4 rdba: 0x010000ab (4/171) class: 1 ba: 0x407a6000
set: 3 pool 3 bsz: 8192 bsi: 0 sflg: 2 pwc: 250,28
dbwrid: 0 obj: 73419 objn: 73419 tsn: 4 afn: 4 hint: f
hash: [0x4e381510,0x3ebebeb4] lru: [0x4dc608d8,0x447f8c44]
lru-flags: moved_to_tail
ckptq: [NULL] fileq: [NULL] objq: [NULL]
st: CR md: NULL tch: 2
cr: [scn: 0x0.d724f],[xid: 0x0.0.0],[uba: 0x0.0.0],[cls: 0x0.d724f],[sfl: 0x0],[lc: 0x0.cf33b]
flags: only_sequential_access
上面的这两个BH在HASH链上是相互指向的,第一个的0x407fa270指向的是第二个BH(0x407fa1f4)的中间位置,第二个的0x3ebebeb4指向的是第一个BH(0x3ebebe38)中间位置。
0x407fa270-0x407fa1f4=0x7C=124
0x3ebebeb4-0x3ebebe38=0x7C=124
在HASH链上的BH的相互指向不是指向的是另一个BH的地址的头部,而是指向了BH的中间部分。(11G 对于32位系统指向的位置比BH地址偏移个124,对于64为系统偏移是176)
从前面查询x$BH的NXT_HASH和PRV_HASH可以得到这样的结论hash: [NXT_HASH,PRV_HASH](即第一个位置放的是NXT_HASH,第二个位置放的是PRV_HASH)
对比上面两个BH信息,发现当前值的BH是挂在CBC的头部,修改前的值是挂在CBC的尾部