一、队列与共享资源
共享资源可以被多个会话、进程同时访问,因此它的访问需要保护。Oracle中,除了PGA,所有的东西(包括内存、磁盘、CPU、表、索引、事务等等,种类太多,一概用东西两字来代表)都是共享资源。多个进程或会话对共享资源操作时,就需要排队。这里所需要排的队就是队列(Enqueue)。访问不同的共享资源,需要排不同的队。可以这样说,有多少种队列,就有多少种需要保护的共享资源。队列的名字一般是两个字节构成,如TM,TX,JQ,……。具体所有队列的种类、名字,参见V$LOCK视图介绍中的附表。
二、队列标识
我们以TM为例,它是DML队列锁。在对表作DML操作时,需要先在此排队,正式点的说法是:需要先获得TM队列锁。TM也被称作表锁,因为它主要是在对表作操作是获得的。如果数据库中有1000个表,针对这一千个表,并非只有一个队列,要是这样的话也不太合理。1000个表,就应该有1000个TM队列,这样才附合常理。如果一千个队列都叫TM,将来操作时不好区分,因此,需要为这一千个TM队列分别名命,这个名字,也被称为队列标识。TM队列的命名格式为:TM-OID-0。其中OID是Object ID,即对象ID。最后一部分一般都是0。假如AA表的OID是6636,它的队列标识就是TM-6636-0。
每种队列的命名格式各不相同,总的来说是“队列名-ID1-ID2”,ID1和ID2分别是两个参数,对于TM队列来说,ID1是OID,ID2为0。
三、资源结构
继续我们上面的假设,如数据库中有一千个表,这就要对应一千个TM队列。但只有当操作到哪个表了,才会为它建立相关的队列信息。比如会话发布了对AA表的更新操作,需要在SGA中为AA建立相关的TM队列信息。这些相关AA的TM信息,也被称为“资源结构”(KSQRS,KSQ是内核服务队列的简写,RS是Resource Structure的简写,即资源结构)。每一个资源结构维持一个所有者、等待者和转换者的列表。如下图:
资源结构前的即是队列标识,也可以作为资源结构标识(资源标识)。
每一个所有者、等待者和转换者有一个锁结构(Ksqlk),简单点说每一个所有者、等待者和转换者有一个链表,此链表由会话、锁模式等信息构成,具体描述了什么会话以什么模式获得此资源结构。具体如下:
1)如果会话获得锁,锁结构将在所有者列表上
2)如果会话正在等待获得锁,锁结构将在等待者列表上
3)如果锁已被获得,但会话正在等待它被转换到一种不同的模式,锁结构将在转换者列表上
所有资源结构组成一个资源表,资源表和资源结构上的锁都被分配在SGA中。资源表中总的行数由初始化参数ENQUEUE_RESOURCES决定,且资源表中的行可以在X$KSQRS中被看到。正在被使用的将在V$RESOURCE中显示。还可以在v$resource_limit中看到资源结构数量的限制和使用情况:
sid=9 pid=10> select * from v$resource_limit where resource_name = ‘enqueue_resources‘;
RESOURCE_NAME CURRENT_UTILIZATION MAX_UTILIZATION INITIAL_ALLOCATION LIMIT_VALUE
------------------------------ ------------------- --------------- -------------------- -------------
enqueue_resources 32 32 968 UNLIMITED
可以看到,当前使用了32个资源结构,最多时使用了32个资源结构,初始化参数中分配了968个资源结构。最多使用是UNLIMITED,没有限制。由于Oracle 9i采用的算法,我们在X$KSQRS中看到的总行数并不是968,而是992。为确保重用,资源表中未用的资源结构被放置在一个连接列表,称为:Resource Free List。我们可以发布一些更新声明,不要提交,这样占用的TM、TX资源结构一直不会释放。观察X$KSQRS,TM资源结构的占用增多,但视图的总行数不变,还是992。新增的TM、TX资源结构占用了其他已经释放的资源结构。
四、资源结构哈希表
为了在资源表中快速找到某一资源结构,Oracle当然还是要使用HASH算法。Oracle根据资源标识计算HASH值。当然,和Library cache一样,资源表中已被占用的资源结构的HASH值构成了一个个HASH Buckut。
上图就是HASH Bucket和资源结构的图,可以看到和Library cache中的很像。HASH算法吗,所有的HASH算法都会有很多共同点。从上图中可以看到,想访问Hash Bucket,需要Enqueue hash chain闩,它需要保护上图中的HASH表,和每个Hash Bucket后的Hash链。它的数量由隐含参数_enqueue_hash_chain_latches控制。Enqueue hash chain闩类似于Buffer cache chain闩。它们都是一个闩要管理多个哈希桶,不过Enqueue hash chain更特殊,在单CPU环境中,只有一个,却要管理所有的哈希桶,这是因为队列的争用,毕竞比Buffer要小的多。
Oracle中所有关于HASH的算法,都要有HASH表、HASH链和保护HASH链的闩,HASH表不需要保护,这是因为HASH表是大小固定的数组。每一个数组元素就是一个Hash Bucket。每个哈希桶的哈希值也都是事先定好的。因此不存在对哈希桶的更改,如插入一个哈希桶、修改一个哈希桶的哈希值或删除一个未使用的哈希桶等等,这些操作都是不存在的。因此,既然不会有修改操作,哈希表就不需保护。需要保护的是每个哈希桶后的链。每个桶后的链中,都可以挂多个对象,我们可能向链上添加或删除对象,既然有修改,就需要保护。
队列资源Hash表的长度由_enqueue_hash控制,其始值来源于SESSION参数,初始是375。我们的资源表共有992行,而哈希桶只有375个,这必然会有多个资源结构排在某一个哈希桶后面。如果曾经增加过ENQUEUE_RESOURCES的值,也就是说,资源结构比系统默认的还要更多一些,但控制哈希表长度的_enqueue_hash参数值如果不跟着增加。这就意味着每个哈希桶下要挂接更多的资源结构,这有可能因起Enqueue hash chain闩的竞争。如何观察闩的使用情况这是下一章节的内容,这里就不多说了。_enqueue_hash的初始值是根据Sessions参数来定的,计算公式如下:((sessions-10)*2)+55,默认 ((170-10)*2)+55,正好等于375,它并不随ENQUEUE_RESOURCES的增加而增加。
队列、资源结构和锁结构的关系如上图。上图是分别在两个会话中发布如下声明后的结果:
在会话10:sid=10 pid=11> insert into a1 values(1,1,1);
已创建 1 行。
在会话12:sid=12 pid=12> insert into a1 values(2,2,2);
已创建 1 行。
查看哈希表视图:
sid=9 pid=10> select * from v$resource where type =‘TM‘;
ADDR TY ID1 ID2
-------- -- ---------- ----------
7B6D5C40 TM 6657 0
可以看到,有一个TM队列,地址是7B6D5C40,对象ID是6657,这个正是A1表。
查看V$LOCK视图:
sid=13 pid=13> select * from v$lock where sid>=8;
ADDR KADDR SID TY ID1 ID2 LMODE REQUEST CTIME BLOCK
-------- -------- ---------- -- ---------- ---------- ---------- ---------- ---------- ----------
7AF661FC 7AF66308 10 TX 458765 2212 6 0 452 0
7AF3A074 7AF3A088 10 TM 6657 0 3 0 452 0
7AF837AC 7AF838B8 12 TX 327686 2355 6 0 84 0
7AF3A17C 7AF3A190 12 TM 6657 0 3 0 84 0
上面的结果中显示,TM-6657-0有两个锁,地址分别是7AF3A088、7AF3A190。
说明,上图中的信息,部分来自于队列结构的DUMP,声明如下,有兴趣可以自己试试。
sid=10 pid=11> alter session set events ‘immediate trace name enqueues level 4‘;
哈希表的长度,由_enqueue_hash设定,资源结构的数量(也称资源表的长度),由ENQUEUE_RESOURCES设定。锁结构的的数量,也可以由DBA控制,隐含参数_enqueue_locks就是设置锁结构数量的(也称队列锁:Enqueue Lock),默认值2230。可以被看到在X$KSQEQ中,正在被使用的在V$ENQUEUE_LOCK显示。但是,有关TM、TX这两个队列的锁和其他情况的队列锁不一样,除非它们发生了Enqueue等待,否则它们并不在X$KSQEQ结构中显示,因此在X$KSQEQ之上的V$ENQUEUE_LOCK也没有正在使用中的TX、TM队列锁的情况。Oracle使用不同的结构来管理TX和TM排除。TX:X$KTCXB,内核事务控制事务对象,V$TRANSACTION_ENQUEUE的基础视图。它的尺寸由transactions初始化参数控制。有关TX的我们下面专门再说。再看TM:X$KTADM,内核事务访问定义DML锁。X$KTADM的尺寸由DML_LOCKS定义,也就是TM锁的数量。X$KTADM对DBA意义不大,可以为我们提供锁的模式、加锁的时间、正在请求的模式、会话SID等信息,信息量并不比V$LOCK中多。通过上两个X$视图观察正在使用中的TM、TX锁的最好办法是:
set linesize 800
col KTCXBNAM for a20
select * from x$ktcxb where ktcxblkp in (select kaddr from v$lock where type=‘TX‘);
select * from x$ktadm where ksqlkadr in ( select kaddr from v$lock where type=‘TM‘);
我们也可以在V$RESOURCE_LIMIT中看到有关队列锁的使用情况:
sid=9 pid=10> select * from v$resource_limit where resource_name like ‘enqueue%‘;
RESOURCE_NAME CURRENT_UTILIZATION MAX_UTILIZATION INITIAL_ALLOCATION LIMIT_VALUE
------------------------------ ------------------- --------------- -------------------- -------------
enqueue_locks 26 30 2230 2230
enqueue_resources 26 26 1200 UNLIMITED
注意enqueue_locks和transactions、DML_LOCKS的关系,enqueue_locks是总的队列锁的数量,transactions定义了在总的队列锁中,TX锁的数量,DML_LOCKS是总队列锁中TM锁的数量。也就是说transactions和DML_LOCKS是enqueue_locks的一部分。