本文是之前关于《RAC 中锁的管理—Buffer Lock》文章的继续,在这篇文章中,会通过一个简单的例子演示RAC 中buffer lock的工作方式。
测试环境:
Oracle 版本:11.2.0.4(2 节点)。
OS : OLE 5
首先,我们创建了一个测试表,为了简化测试,测试表只包含两行数据。
SQL>create table my_test (id number, name varchar2(20));
SQL>insert into my_test values ( 1, ‘hello’);
SQL>insert into my_test values ( 2, ‘goodby’);
SQL>commit;
接下来,查看表的数据库对象,和对应的数据文件编号和块号。
SQL> select object_id, data_object_id from dba_objects where owner=’SCOTT’ and object_name=’MY_TEST’;
OBJECT_ID DATA_OBJECT_ID
———- ————–
14445 14446
SQL> SELECT DBMS_ROWID.Rowid_relative_fno(ROWID) “FILE”, DBMS_ROWID.Rowid_block_number(ROWID) “BLOCK”, id, name from my_test;
FILE BLOCK ID NAME
———- ———- ———- ——————–
5 4741 1 hello
5 4741 2 goodby
看起来创建的测试表(my_test)的DATA_OBJECT_ID=14446, 其中的两行数据都保存在5号数据文件的4741号数据块。
下面介绍一下测试过程中会使用的一些视图和表。
v$bh : 这个视图保存了buffer header在内存中对应的信息,它能够帮助我们找到和buffer 相关的信息。
V$GC_ELEMENT : 这个视图保存了和 Global Cache element 相关的信息,我一般把它称之为LE(Lock Element),因为这里面主要就是保存和 GC相关的锁元素的信息。
x$kjbr : 这个基表(或者可以叫做内存结构的映射)记录了块资源(Buffer Resource)的更多信息。
x$kjbl: 这个基表记录了和br 资源相关的锁的信息(buffer lock)。
接下来,就通过一系列的操作来说明RAC的GC 部分的工作方式。
步骤1:在实例1上创建一个会话并执行下面的查询,之后看一下对应的PCM锁的状况。
会话1:
SQL> select * from my_test where id=1;
ID NAME
———- ——————–
1 hello
会话2:
SQL> select b.file#, b.block#, b.status, b.LOCK_ELEMENT_NAME,b.FORCED_READS, b.FORCED_WRITES, g.MODE_HELD, g.BLOCK_COUNT, g.LOCAL, g.flags
from v$bh b, V$GC_ELEMENT g
where b.LOCK_ELEMENT_ADDR=g.GC_ELEMENT_ADDR and
b.file#=5 and b.block#=4741;
FILE# BLOCK# STATUS LOCK_ELEMENT_NAME FORCED_READS FORCED_WRITES
———- ———- ———- —————– ———— ————-
MODE_HELD BLOCK_COUNT LOCAL FLAGS
———- ———– ———- ———-
5 4741 scur 4741 0 0
1 1 1 32
可以看到被访问的块,在本地节点处于shared current(scur)模式。
SQL> select KJBLNAME,INST_ID, KJBLLOCKP, KJBLGRANT,KJBLREQUEST,KJBLROLE,KJBLMASTER
from x$kjbl
where KJBLNAME like ‘%1285%5%BL%’ and KJBLPKEY=14446;
KJBLNAME INST_ID KJBLLOCK KJBLGRANT KJBLREQUE
—————————— ———- ——– ——— ———
KJBLROLE KJBLMASTER
———- ———-
[0x1285][0x5],[BL][ext 0x0,0x0 1 323E3AB0 KJUSERPR KJUSERNL
0 1
看起来节点1的确以KJUSERPR的方式持有了这个buffer。
SQL> select KJBRRESP, KJBRGRANT, KJBRNCVL, KJBRROLE, KJBRNAME, KJBRMASTER, KJBRGRANTQ, KJBRCVTQ, KJBRWRITER,KJBRSID, KJBRPKEY
from x$kjbr
where KJBRNAME like ‘%1285%5%BL%’ and KJBRPKEY=14446;
no rows selected
这里的结果比较奇怪,实例1上显示它并没有这个块资源对应的信息。其实这是正常的,因为每个资源的信息只有在它的master节点上才能找到,而每个资源的master节点是通过hash函数计算出来的,所以它不一定会保存在节点1上。我的测试是在一个2节点的rac上做的,所以在实例2运行同样的查询就会找到这个资源的详细信息。 在实例2上运行同样的查询。
实例2:
SQL> select KJBRRESP, KJBRGRANT, KJBRNCVL, KJBRROLE, KJBRNAME, KJBRMASTER, KJBRGRANTQ, KJBRCVTQ, KJBRWRITER,KJBRSID, KJBRPKEY
from x$kjbr
where KJBRNAME like ‘%1285%5%BL%’ and KJBRPKEY=14446;
KJBRRESP KJBRGRANT KJBRNCVL KJBRROLE KJBRNAME
——– ——— ——— ———- ——————————
KJBRMASTER KJBRGRAN KJBRCVTQ KJBRWRIT KJBRSID KJBRPKEY
———- ——– ——– ——– ———- ———-
3B3976D8 KJUSERPR KJUSERNL 0 [0x1285][0x5],[BL][ext 0x0,0x0
1 3A6316D8 00 00 0 14446
能够看到,实例2是这个buffer资源的主节点。一般来说,一个buffer资源的名称是由[block#][file#],[BL]构成的,当然他们是16进制(hex)的,1285(hex)=4741(dec),所以我的查询条件是“KJBRNAME like ‘%1285%5%BL%’ and KJBRPKEY=14446;”。上面的信息显示这个资源的master在节点2,并且已经被用KJUSERPR的方式被赋权了,而对应的convert请求不存在。
步骤2:在实例2 查询表里的另一行数据。由于两行数据保存在相同的数据块,所以这两个进程访问的资源也是相同的。
会话1:
SQL> select * from my_test where id=2;
ID NAME
———- ——————–
2 goodby
会话2:
select b.file#, b.block#, b.status, b.LOCK_ELEMENT_NAME,b.FORCED_READS, b.FORCED_WRITES, g.MODE_HELD, g.BLOCK_COUNT, g.LOCAL, g.flags
from v$bh b, V$GC_ELEMENT g
where b.LOCK_ELEMENT_ADDR=g.GC_ELEMENT_ADDR and
b.file#=5 and b.block#=4741;
FILE# BLOCK# STATUS LOCK_ELEMENT_NAME FORCED_READS FORCED_WRITES
———- ———- ———- —————– ———— ————-
MODE_HELD BLOCK_COUNT LOCAL FLAGS
———- ———– ———- ———-
5 4741 scur 4741 0 0
1 1 1 32
看起来实例2也同样以scur的方式持有了这个块。
SQL> select KJBLNAME,INST_ID, KJBLLOCKP, KJBLGRANT,KJBLREQUEST,KJBLROLE,KJBLMASTER
from x$kjbl
where KJBLNAME like ‘%1285%5%BL%’ and KJBLPKEY=14446;
KJBLNAME INST_ID KJBLLOCK KJBLGRANT KJBLREQUE
—————————— ———- ——– ——— ———
KJBLROLE KJBLMASTER
———- ———-
[0x1285][0x5],[BL][ext 0x0,0x0 2 2F3E9770 KJUSERPR KJUSERNL
0 1
[0x1285][0x5],[BL][ext 0x0,0x0 2 3A6316D8 KJUSERPR KJUSERNL
0 1
这里显示了两行记录,其实是正常的现象,因为节点2是这个buffer资源的master节点,所以他就会有这个buffer上的所有锁的信息。而节点1不是master节点,所以它就只有自己节点所持有的所信息。
步骤3:回到实例1,对表中的第一行数据进行修改,这意味着我们要获得这个资源上更高级别的锁。
SQL>update my_test set name=’hello world’ where id=1;
SQL>select b.file#, b.block#, b.status, b.LOCK_ELEMENT_NAME,b.FORCED_READS, b.FORCED_WRITES, g.MODE_HELD, g.BLOCK_COUNT, g.LOCAL, g.flags
from v$bh b, V$GC_ELEMENT g
where b.LOCK_ELEMENT_ADDR=g.GC_ELEMENT_ADDR and
b.file#=5 and b.block#=4741;
FILE# BLOCK# STATUS LOCK_ELEMENT_NAME FORCED_READS FORCED_WRITES
———- ———- ———- —————– ———— ————-
MODE_HELD BLOCK_COUNT LOCAL FLAGS
———- ———– ———- ———-
5 4741 xcur 4741 0 0
2 1 1 32
能看到,这个数据块当前的状态变成了exclusive current(xcur),也就是说,本地实例拥有的是这个块的最新���本。我们在实例2下面的语句。
实例2:
SQL>select b.file#, b.block#, b.status, b.LOCK_ELEMENT_NAME,b.FORCED_READS, b.FORCED_WRITES
from v$bh b
where b.file#=5 and b.block#=4741;
FILE# BLOCK# STATUS LOCK_ELEMENT_NAME FORCED_READS FORCED_WRITES
———- ———- ———- —————– ———— ————-
5 4741 cr 0 0
可以看到,由于实例1修改了这个数据块(虽让是不同的行),实例2对应的块也变成了cr。
再来看一下资源和锁层面的变化。
实例2:
SQL> select KJBRRESP, KJBRGRANT, KJBRNCVL, KJBRROLE, KJBRNAME, KJBRMASTER, KJBRGRANTQ, KJBRCVTQ, KJBRWRITER,KJBRSID, KJBRPKEY
from x$kjbr
where KJBRNAME like ‘%1285%5%BL%’ and KJBRPKEY=14446;
KJBRRESP KJBRGRANT KJBRNCVL KJBRROLE KJBRNAME
——– ——— ——— ———- ——————————
KJBRMASTER KJBRGRAN KJBRCVTQ KJBRWRIT KJBRSID KJBRPKEY
———- ——– ——– ——– ———- ———-
3B3976D8 KJUSEREX KJUSERNL 0 [0x1285][0x5],[BL][ext 0x0,0x0
1 3A6316D8 00 00 0 14446
在这个资源的master节点上,能够看到这个资源已经被以KJUSEREX方式被授权了。
实例1:
SQL> select KJBLNAME,INST_ID, KJBLLOCKP, KJBLGRANT,KJBLREQUEST,KJBLROLE,KJBLMASTER
from x$kjbl
where KJBLNAME like ‘%1285%5%BL%’ and KJBLPKEY=14446;
KJBLNAME INST_ID KJBLLOCK KJBLGRANT KJBLREQUE
—————————— ———- ——– ——— ———
KJBLROLE KJBLMASTER
———- ———-
[0x1285][0x5],[BL][ext 0x0,0x0 1 323E3AB0 KJUSEREX KJUSERNL
0 1
实例1以KJUSEREX方式持有了这个buffer资源上的锁。
实例2:
SQL>select KJBLNAME,INST_ID, KJBLLOCKP, KJBLGRANT,KJBLREQUEST,KJBLROLE,KJBLMASTER
from x$kjbl
where KJBLNAME like ‘%1285%5%BL%’ and KJBLPKEY=14446;
2 3
KJBLNAME INST_ID KJBLLOCK KJBLGRANT KJBLREQUE
—————————— ———- ——– ——— ———
KJBLROLE KJBLMASTER
———- ———-
[0x1285][0x5],[BL][ext 0x0,0x0 2 3A6316D8 KJUSEREX KJUSERNL
0 1
这个时候资源主节点上也只有1行数据了,只是因为实例要求持有更高级别的锁KJUSEREX,那么之前的低级别锁KJUSERPR就需要被释放以便能够让实例1获得更高级别的锁。
步骤4:在实例2当中修改表的第二行数据。
SQL> update my_test set name=’goodby my love’ where id=2;
SQL> select b.file#, b.block#, b.status, b.LOCK_ELEMENT_NAME,b.FORCED_READS, b.FORCED_WRITES, g.MODE_HELD, g.BLOCK_COUNT, g.LOCAL, g.flags
from v$bh b, V$GC_ELEMENT g
where b.LOCK_ELEMENT_ADDR=g.GC_ELEMENT_ADDR and
b.file#=5 and b.block#=4741;
FILE# BLOCK# STATUS LOCK_ELEMENT_NAME FORCED_READS FORCED_WRITES
———- ———- ———- —————– ———— ————-
MODE_HELD BLOCK_COUNT LOCAL FLAGS
———- ———– ———- ———-
5 4741 xcur 4741 0 0
2 1 0 32
可以看到,由于实例2 修改了这个块,所以实例2 上的这个块处于了xcur 模式。
实例1:
SQL> select b.file#, b.block#, b.status, b.LOCK_ELEMENT_NAME,b.FORCED_READS, b.FORCED_WRITES
from v$bh b
where b.file#=5 and b.block#=4741;
FILE# BLOCK# STATUS LOCK_ELEMENT_NAME FORCED_READS FORCED_WRITES
———- ———- ———- —————– ———— ————-
5 4741 pi 4741 0 0
5 4741 free 0 0
5 4741 cr 0 0
5 4741 cr 0 0
可以看到,实例1当中出现了一个PI(Past Image)类型的buffer。简单地说,当一个数据块在多个实例被修改的时候,除了最新修改该块的实例,其他实例对应的块都会变成PI。而且,再把对应的buffer标识成PI之前,还要把buffer 拷贝成另一个CR。当然,一个buffer在数据库缓冲区当中拥有的CR copy数量是通过隐含参_DB_BLOCK_MAX_CR_DBA(默认值=6)来控制的。
再来看看资源和锁的信息。
实例2:
SQL> select KJBRRESP, KJBRGRANT, KJBRNCVL, KJBRROLE, KJBRNAME, KJBRMASTER, KJBRGRANTQ, KJBRCVTQ, KJBRWRITER,KJBRSID, KJBRPKEY
from x$kjbr
where KJBRNAME like ‘%1285%5%BL%’ and KJBRPKEY=14446;
2 3
KJBRRESP KJBRGRANT KJBRNCVL KJBRROLE KJBRNAME
——– ——— ——— ———- ——————————
KJBRMASTER KJBRGRAN KJBRCVTQ KJBRWRIT KJBRSID KJBRPKEY
———- ——– ——– ——– ———- ———-
3B3976D8 KJUSEREX KJUSERNL 0 [0x1285][0x5],[BL][ext 0x0,0x0
1 2F3E9770 00 00 0 14446
资源的状态信息并没有太大的变化。
实例2:
SQL> select KJBLNAME,INST_ID, KJBLLOCKP, KJBLGRANT,KJBLREQUEST,KJBLROLE,KJBLMASTER
from x$kjbl
where KJBLNAME like ‘%1285%5%BL%’ and KJBLPKEY=14446;
2 3
KJBLNAME INST_ID KJBLLOCK KJBLGRANT KJBLREQUE
—————————— ———- ——– ——— ———
KJBLROLE KJBLMASTER
———- ———-
[0x1285][0x5],[BL][ext 0x0,0x0 2 2F3E9770 KJUSEREX KJUSERNL
0 1
由于实例2,最新修改了记录,所以它持有了KJUSEREX级别的buffer lock
实例1:
SQL> select KJBLNAME,INST_ID, KJBLLOCKP, KJBLGRANT,KJBLREQUEST,KJBLROLE,KJBLMASTER
from x$kjbl
where KJBLNAME like ‘%1285%5%BL%’ and KJBLPKEY=14446;
no rows selected
由于KJUSEREX级别的锁是不能和其他锁兼容的,所以之前实例1持有的锁被释放掉了。
由于篇幅有限,今天的测试先到此为止。希望以上的测试有助于大家理解oracle RAC 中buffer lock的工作方式
如果大家对这篇文章有任何问题或者需要展开讨论,请在本贴回复。