本文是之前关于《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 相关的信息,我一般把它称之为LELock Element),因为这里面主要就是保存和 GC相关的锁元素的信息。

x$kjbr 这个基表(或者可以叫做内存结构的映射)记录了块资源(Buffer Resource)的更多信息。

x$kjbl 这个基表记录了和br 资源相关的锁的信息(buffer lock)。

接下来,就通过一系列的操作来说明RACGC 部分的工作方式。

步骤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)的,1285hex=4741dec),所以我的查询条件是“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

实例1KJUSEREX方式持有了这个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当中出现了一个PIPast 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的工作方式

如果大家对这篇文章有任何问题或者需要展开讨论,请在本贴回复。