TYPE
VARCHAR(2)
区分该锁保护对象的类型;
ID1
NUMBER
锁标识1;
ID2
NUMBER
锁标识2;
LMODE
NUMBER
锁模式:0(None),1(null),2(row share), 3(row exclusive),4 (share),5(share row exclusive),6(exclusive)
REQUEST
NUMBER
申请的锁模式:具体值同上面的LMODE
CTIME
NUMBER
已持有或等待锁的时间;
BLOCK
NUMBER
是否阻塞其它锁申请;
表三:v$lock视图主要字段说明
其中在TYPE字段的取值中,本文只关心TM、TX两种DML锁类型;
关于ID1、ID2,TYPE取值不同其含义也有所不同:
TYPE
ID1
ID2
TM
被修改表的标识(object_id)
0
TX
以十进制数值表示该事务所占用的回滚段号与该事务在该回滚段的事务表(Transaction table)中所占用的槽号(slot number,可理解为记录号)。其组成形式为: 0xRRRRSSSS ( RRRR = RBS number, SSSS = slot )。
以十进制数值表示环绕(wrap)次数,即该槽(slot)被重用的次数;
表四:v$lock视图中ID1与ID2字段取值说明
3.1.2 v$locked_object视图
v$locked_object视图列出当前系统中哪些对象正被锁定,其主要字段说明如下:
字段名称
类型
说明
XIDUSN
NUMBER
回滚段号;
XIDSLOT
NUMBER
槽号;
XIDSQN
NUMBER
序列号;
OBJECT_ID
NUMBER
被锁对象标识;
SESSION_ID
NUMBER
持有锁的会话(SESSION)标识;
ORACLE_USERNAME
VARCHAR2(30)
持有该锁的用户的Oracle用户名;
OS_USER_NAME
VARCHAR2(15)
持有该锁的用户的操作系统用户名;
PROCESS
VARCHAR2(9)
操作系统的进程号;
LOCKED_MODE
NUMBER
锁模式,取值同表三中的LMODE;
表五:v$locked_object视图字段说明
3.2 监控脚本
根据上述系统视图,可以编制脚本来监控数据库中锁的状况。
3.2.1 showlock.sql
第一个脚本showlock.sql,该脚本通过连接v$locked_object与all_objects两视图,显示哪些对象被哪些会话锁住:
/* showlock.sql */
column o_name format a10
column lock_type format a20
column object_name format a15
select rpad(oracle_username,10) o_name,session_id sid,
decode(locked_mode,0,'None',1,'Null',2,'Row share',
3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,'Exclusive') lock_type,
object_name ,xidusn,xidslot,xidsqn
from v$locked_object,all_objects
where v$locked_object.object_id=all_objects.object_id;
3.2.2 showalllock.sql
第二个脚本showalllock.sql,该脚本主要显示当前所有TM、TX锁的信息;
/* showalllock.sql */
select sid,type,id1,id2,
decode(lmode,0,'None',1,'Null',2,'Row share',
3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,'Exclusive')
lock_type,request,ctime,block
from v$lock
where TYPE IN('TX','TM');
4 Oracle 多粒度封锁机制示例
以下示例均运行在Oracle 8.1.7上,数据库版本不同,其输出结果也可能有所不同。首先建立3个会话,其中两个(以下用SESS#1、SESS#2表示)以SCOTT用户连入数据库,以操作Oracle提供的示例表(DEPT、EMP);另一个(以下用SESS#3表示)以SYS用户连入数据库,用于监控;
4.1 操作同一行数据引发的锁阻塞
SESS#1:
SQL> select * from dept for update;
DEPTNO DNAME LOC
---------- -------------- -------------
10 account 70
20 research 8
30 sales 8
40 operations 8
SESS#3:
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 17 Row share DEPT 8 2 5861
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
17 TX 524290 5861 Exclusive 0 761 0
17 TM 32970 0 Row share 0 761 0
如第一个脚本showlock所示,执行完SELECT…FOR UPDATE 语句后, SESS#1(SID为17)在DEPT表上获得Row share锁;如第二个脚本showalllock所示,SESS#1获得的TX锁为Exclusive,这些都验证了上面的理论分析。另外,我们可以将TX锁的ID1按如下方法进行分解:
SQL> select trunc(524290/65536) xidusn,mod(524290,65536) xidslot from dual;
XIDUSN XIDSLOT
------ -------
8 2
分解结果与第一个脚本直接查出来的XIDUSN与XIDSLOT相同,而TX锁的ID2(5861)与XIDSQN相同,可见当LOCK TYPE为TX时,ID1实际上是该事务所占用的回滚段段号与事务表中的槽(SLOT)号的组合,ID2即为该槽被重用的次数,而这三个值实际上可以唯一地标识一个事务,即TRANSACTION ID,这三个值从系统表v$transaction中也可查到。
另外, DEPT表中有4条记录被锁定,但TX锁只有1个,这也与上面的理论分析一致。继续进行操作:
SESS#2:
SQL> update dept set loc=loc where deptno=20;
该更新语句被阻塞,此时再查看系统的锁情况:
SESS#3:
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 17 Row share DEPT 8 2 5861
SCOTT 19 Row Exclusive DEPT 0 0 0
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
17 TX 524290 5861 Exclusive 0 3462 1
17 TM 32970 0 Row share 0 3462 0
19 TM 32970 0 Row Exclusive 0 7 0
19 TX 524290 5861 None 6 7 0
在DEPT表上除了SESS#1(SID为17)持有Row share锁外,又增加了SESS#2(SID为19)持有的Row Exclusive锁,但还没有为SESS#2分配回滚段(XIDUSN、XIDSLOT、XIDSQN的值均为0);而从第二个脚本看到,SESS#2的TX锁的LOCK_TYPE为None,其申请的锁类型(REQUEST)为6(即Exclusive),而其ID1、ID2的值与SESS#1所持有的TX锁的ID1、ID2相同,SESS#1的TX锁的阻塞域(BLOCK)为1,这就说明了由于SESS#1持有的TX锁,阻塞了SESS#2的更新操作(SESS#2所更新的行与SESS#1所锁定的行相冲突)。还可以看出,SESS#2先申请表级的TM锁,后申请行(事务)级的TX锁,这也与前面的理论分析一致。
下面,将SESS#1的事务进行回滚,解除对SESS#2的阻塞,再对系统进行监控。
SESS#3:
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 19 Row Exclusive DEPT 2 10 5803
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
19 TX 131082 5803 Exclusive 0 157 0
19 TM 32970 0 Row Exclusive 0 333 0
可以看到,SESS#1的事务所持有的锁已经释放,系统为SESS#2的事务分配了回滚段,而其TX锁也已经获得,并且ID1、ID2是其真正的Transaction ID。再将会话2的事务进行回滚。
SESS#2:
SQL> rollback;
Rollback complete.
检查系统锁的情况:
SESS#3:
SQL> @showlock
no rows selected
SQL> @showalllock
no rows selected
可以看到,TM与TX锁已全部被释放。
4.2 实体完整性引发的锁阻塞
DEPT(部门)表有如下字段DEPTNO(部门编号),DNAME(部门名称),LOC(部门位置);其中DEPTNO列为主键。
SESS#1
SQL> INSERT INTO DEPT(DEPTNO) VALUES(50);
1 row created.
SESS#3
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 7 Row Exclusive DEPT 6 88 29
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
7 TX 393304 29 Exclusive 0 6 0
7 TM 3574 0 Row Exclusive 0 6 0
向DEPT表中插入一条DEPTNO为50的记录后,SESS#1(SID为7)在DEPT表上获得Row Exclusive锁,并且由于进行了数据插入,该事务被分配了回滚段,获得TX锁。
SESS#2
INSERT INTO DEPT(DEPTNO) VALUES(50);
这时,SESS#2(SID为8)也向DEPT表中插入一条DEPTNO为50的记录,该语句被阻塞,检查锁情况:
SESS#3
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 8 Row Exclusive DEPT 7 75 30
SCOTT 7 Row Exclusive DEPT 6 88 29
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
7 TX 393304 29 Exclusive 0 92 1
7 TM 3574 0 Row Exclusive 0 92 0
8 TX 458827 30 Exclusive 0 22 0
8 TM 3574 0 Row Exclusive 0 22 0
8 TX 393304 29 None 4 22 0
SESS#2在DEPT表上也获得了Row Exclusive锁,同样也获得了回滚段的分配,得到TX锁,但是由于其插入的记录与SESS#1插入的记录的DEPTNO均为50,该语句成功与否取决于SESS#1的事务是提交还是回滚,所以SESS#2被阻塞,表现为SESS#2以Share方式(REQUEST=4)等待SESS#1所持有的TX锁的释放。
这时,如果SESS#1进行回滚:
SESS#1
SQL> ROLLBACK;
Rollback complete.
SESS#2
1 row created.
SESS#3
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 8 Row Exclusive DEPT 7 75 30
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
8 TX 458827 30 Exclusive 0 136 0
8 TM 3574 0 Row Exclusive 0 136 0
SESS#2的阻塞将被解除,SESS#2只持有原先已有的TM与TX锁,其等待的TX锁(由SESS#1持有)也消失了。
如果SESS#1提交而不是回滚,在SESS#2上将会出现如下提示:
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.PK_DEPT) violated错误。
即发生主键冲突,SESS#1与SESS#2的所有锁资源均被释放。
4.3 参照完整性引发的锁阻塞
EMP(员工)表有如下字段:EMPNO(员工编号),ENAME(员工姓名),DEPTNO(员工所在部门编号),其中DEPTNO列为外键,其父表为DEPT。
SESS#1
SQL> insert into dept(deptno) values(60);
1 row created.
SESS#3
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 7 Row Exclusive DEPT 2 6 33
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
7 TX 131078 33 Exclusive 0 148 0
7 TM 3574 0 Row Exclusive 0 148 0
SESS#1(SID为7)在DEPT表中先插入一条DEPTNO为60的记录,SESS#1获得了DEPT表上的Row Exclusive锁,及一个TX锁。
SESS#2
insert into emp(empno,deptno) values(2000,60);
被阻塞
SESS#3
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 7 Row Exclusive DEPT 2 6 33
SCOTT 8 Row Exclusive EMP 3 20 31
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
7 TX 131078 33 Exclusive 0 228 1
7 TM 3574 0 Row Exclusive 0 228 0
8 TX 196628 31 Exclusive 0 9 0
8 TM 3576 0 Row Exclusive 0 9 0
8 TX 131078 33 None 4 9 0
SESS#2(SID为8)向EMP表中出入一条新记录,该记录DEPT值为60(即SESS#1刚插入,但还未提交的记录的DEPTNO值),SESS#2获得了EMP表上的Row Exclusive锁,另外由于插入记录,还分配了回滚段及一个TX锁,但由于SESS#2的插入语句是否成功取决于SESS#1的事务是否进行提交,所以它被阻塞,表现为SESS#2以Share(REQUEST=4)方式等待SESS#1释放其持有的TX锁。这时SESS#1如果提交,SESS#2的插入也将执行成功,而如果SESS#1回滚,由于不符合参照完整性,SESS#2将报错:
SESS#2
insert into emp(empno,deptno) values(2000,60)
*
ERROR at line 1:
ORA-02291: integrity constraint (SCOTT.FK_DEPTNO) violated - parent key not
Found
SESS#2持有的锁也被全部释放。
4.4 外键未加索引引发的锁阻塞
EMP表上的DEPTNO列为外键,但没有在该列上建索引。
SESS#1
SQL> delete emp where 0=1;
0 rows deleted.
SESS#3:
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 7 Row Exclusive EMP 0 0 0
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
7 TM 3576 0 Row Exclusive 0 10 0
首先SESS#1(SID为7)做了一个删除操作,但由于条件(0=1)为永假,所以实际上并没有一行被删除,从监控脚本可以看出SESS#1在EMP表上获得Row Exclusive锁,但由于没有实际的行被删除,所以并没有TX锁,也没有为SESS#1分配回滚段。
SESS#2:
SQL> delete dept where 0=1;
该语句虽然也不会删除实际数据,但却被阻塞,查看系统的锁情况:
SESS#3:
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 8 None EMP 0 0 0
SCOTT 7 Row Exclusive EMP 0 0 0
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
7 TM 3576 0 Row Exclusive 0 31 1
8 TM 3576 0 None 4 12 0
SESS#2申请在EMP表上加SHARE锁(REQUEST=4),但该申请被SESS#1阻塞,因为SESS#1已经在EMP表上获得了Row Exclusive锁,与SHARE锁不相容。
下面我们对SESS#1进行回滚后,再进行监控。
SESS#3:
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 8 Share EMP 0 0 0
SCOTT 8 Row Exclusive DEPT 0 0 0
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
8 TM 3574 0 Row Exclusive 0 16 0
8 TM 3576 0 Share 0 16 0
SESS#2在EMP表上获得Share锁后,又在DEPT表上获得Row Exclusive锁,由于没有实际的行被修改,SESS#2并没有获得TX锁。
在Oracle8中,如果子表的外键上没有加索引,当在父表上删除记录时,会先在子表上申请获得Share锁,之后再在父表上申请Row Exclusive锁。由于表级Share锁的封锁粒度较大,所以容易引起阻塞,从而造成性能问题。
当在外键上建立索引后,在父表上删除数据将不再对子表上加Share锁,如下所示:
SESS#1:
SQL> create index i_emp_deptno on emp(deptno);
Index created.
SQL> delete dept where 0=1;
0 rows deleted.
SQL>
SQL> @showlock
O_NAME SID LOCK_TYPE OBJECT_NAME XIDUSN XIDSLOT XIDSQN
---------- ----- --------------- --------------- ------ ------- ------
SCOTT 7 Row Exclusive DEPT 0 0 0
SQL> @showalllock
SID TY ID1 ID2 LOCK_TYPE REQUEST CTIME BLOCK
----- -- ---------- ---------- --------------- ---------- ---------- ----------
7 TM 3574 0 Row Exclusive 0 9 0
可以看到,在EMP表DEPTNO列上建立索引后,在DEPT表上执行DELETE操作,不再要求在EMP表上加Share锁,只是在DEPT表上加Row Exclusive锁,封锁的粒度减小,引起阻塞的可能性也减小。
5 Oracle 多粒度封锁机制总结
Oracle通过具有意向锁的多粒度封锁机制进行并发控制,保证数据的一致性。其DML锁(数据锁)分为两个层次(粒度):即表级和行级。通常的DML操作在表级获得的只是意向锁(RS或RX),其真正的封锁粒度还是在行级;另外,在Oracle数据库中,单纯地读数据(SELECT)并不加锁,这些都极大地提高了系统的并发程度。
在支持高并发度的同时,Oracle利用意向锁及数据行上加锁标志位等设计技巧,减小了Oracle维护行级锁的开销,使其在数据库并发控制方面有着明显的优势。