2、检测并解决冲突

我们先执行下面的语句获得当前session的SID号,然后执行DML语句:

SQL> select sid from v$mystat where rownum=1;

SID

----------

159

SQL> update employees set last_name=last_name||'a'

where department_id=60;

6 rows updated.

结果显示出,该事务更新了6条记录。然后我们来检查该事务的状态:

SQL> select xidusn,xidslot,xidsqn,status from v$transaction;

xIDUSN    xIDSLOT     xIDSQN    STATUS

---------- ---------- ---------- ----------------

6           36       5839   ACTIVE

 

我们看到该事务使用了6号回滚段,槽号为36号。然后我们观察v$lock视图:

SQL> select sid,type,id1,id2,decode(lmode,0,'None',1,'Null',

2,'Row share',

2          3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',

6,'Exclusive') lock_mode,

3          decode(request,0,'None',1,'Null',2,'Row share',

4          3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',

6,'Exclusive') request_ mode,block

5  from v$lock

6  where sid=159;

SID   TYPE     ID1      ID2     LOCK_MODE       REQUEST_MODE   BLOCK

---- ----- -------- ----------  -------------- -------------  ------

159   TM         53777           0   Row Exclusive      None       0

159   TX        393252       5839   Exclusive        None          0

 

从输出结果中我们可以看到两个级别的锁:TX和TM。对于TX来说,Oracle只有一种模式,也就是排他模式。同时我们应该会注意到,该事务更新了6条记录, 但是只有一个行级锁,并不是会产生6个行级锁。事实上,TX锁也叫做事务锁(transaction,其中的X表示事务)。也就是说,一个事务只会产生一个TX锁。 而对于TM锁定来说,其锁定模式为行级排他锁。

 

对于TM锁来说,ID1表示被锁定的对象的对象ID,ID2始终为0。如下所示:

SQL> select object_name from dba_objects where object_id=53777;

OBJECT_NAME

-------------------------------

EMPLOYEES

 

对于TX锁来说,ID1表示事务使用的回滚段编号以及在事务表中对应的记录编号,ID2表示该记录编号被重用的次数(wrap)。使用下面的SQL语句将上面的 ID1(393252)转换为直观的数字:

SQL> select trunc(393252/power(2,16)) as undo_blk#,

2         bitand(393252,to_number('ffff','xxxx')) + 0

as slot#

3  from dual;

UNDO_BLK#      SLOT#

---------- ----------

6           36

可以看到,该值与v$transaction中记录的值是一样的。

 

我们再启动一个session,获得SID号以后,执行下面的DML语句:

SQL> select sid from v$mystat where rownum=1;

SID

----------

131

SQL> update employees set last_name=last_name||'b' where

department_id=60;

131号session所发出的DML语句被阻塞了,无法执行下去。我们执行下面的SQL语句:

SQL> select sid,type,id1,id2,decode(lmode,0,'None',1,'Null',2,'

Row share',

2          3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',

6,'Exclusive') lock_mode,

3          decode(request,0,'None',1,'Null',2,'Row share',

4          3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',

6,'Exclusive') request_ mode,block

5  from v$lock

6  where sid in(159,131)

7  order by sid;

SID   TYPE   ID1       ID2          LOCK_MODE            REQUEST

_MODE    BLOCK

----  ----- -------- ----------  --------------     -------------

-------

131    TM       53777            0    Row Exclusive     None         0

131    TX      393252        5839     None               Exclusive   0

159    TX      393252        5839     Exclusive         None         1

159    TM       53777            0     Row Exclusive    None         0

可以看到,131号session在TX锁的请求模式(request_mode列)上为排他锁,因为131号session要更新记录,就必须获得被更新记录上的排他锁。但是由于 这些记录正在被159号session锁定,没能获得行级锁,因此其锁定模式(lock_mode)为None。有趣的是,这时131号session的事务信息(ID1与ID2的值)与 159号session的事务信息是完全一样的,因为131号session的事务由于无法获得锁定,因此还没能开始。131号session在TM锁定上,已经获得了行级排他锁 。因为159号session在表上添加的RX锁定,并不会阻止其他session在相同表上添加RX锁。

 

我们可以再次启动另一个session,并执行下面的DML语句:

SQL> select sid from v$mystat where rownum=1;

SID

----------

135

SQL> update employees set last_name=last_name||'c' where

department_id=60;

 

这时,我们查询v$enqueue_lock来获得锁定队列中的session信息。

SQL> select sid,type,decode(request,0,'None',1,'Null',2,'Row share',

2         3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,

'Exclusive') request_mode

3  from v$enqueue_lock

4  where sid in(131,135);

SID            TYPE    REQUEST_MODE

----------   ----    -------------------

131            TX       Exclusive

135            TX       Exclusive

 

可以看到,131号session先进入队列,而135号session后进入队列。我们还可以使用下面的SQL语句将session之间的阻塞关系显示得更加清楚:

SQL> select a.sid blocker_sid,a.serial#,a.username as blocker_username,

2         b.type,decode(b.lmode,0,'None',1,'Null',2,'Row share',

3          3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,

'Exclusive') lock_mode,

4         b.ctime as time_held,c.sid as waiter_sid,

5         decode(c.request,0,'None',1,'Null',2,'Row share',

6          3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,

'Exclusive') request_mode,

7         c.ctime time_waited

8  from   v$lock b, v$enqueue_lock c, v$session a

9  where  a.sid     = b.sid

10  and    b.id1     = c.id1(+)

11  and    b.id2     = c.id2(+)

12  and    c.type(+) = 'TX'

13  and    b.type    = 'TX'

14  and    b.block   = 1

15  order by time_held, time_waited;

BLOCKER_SID    SERIAL#   BLOCKER_USERNAME     TY       LOCK_MODE

-----------    --------  ----------------     -----   ----------

159                5    HR                      TX       Exclusive

159                5    HR                      TX       Exclusive

TIME_HELD WAITER_SID   REQUEST_MODE     TIME_WAITED

---------   ----------   -----------      -----------

5488           135    Exclusive                    5

5488           131    Exclusive                 2770

 

从输出结果中可以很明显地看出,159号session阻塞了135号和131号session。要解决该锁定冲突,我们只需要让159号session提交事务即可,提交以后再次 查询v$lock:

SQL> select a.sid blocker_sid,a.serial#,a.username as blocker_username,

2         b.type,decode(b.lmode,0,'None',1,'Null',2,'Row share',

3          3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,

'Exclusive') lock_mode,

4         b.ctime as time_held,c.sid as waiter_sid,

5         decode(c.request,0,'None',1,'Null',2,'Row share',

6          3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',

6,'Exclusive') request_ mode,

7         c.ctime time_waited

8  from   v$lock b, v$enqueue_lock c, v$session a

9  where  a.sid     = b.sid

10  and    b.id1     = c.id1(+)

11  and    b.id2     = c.id2(+)

12  and    c.type(+) = 'TX'

13  and    b.type    = 'TX'

14  and    b.block   = 1

15  order by time_held, time_waited;

BLOCKER_SID    SERIAL#   BLOCKER_USERNAME     TY       LOCK_MODE

-----------    --------  ----------------     -----   ----------

131                6    HR                      TX      Exclusive

TIME_HELD WAITER_SID   REQUEST_MODE     TIME_WAITED

---------   ----------   -----------      -----------

357            135   Exclusive                  215

 

由于在Oracle中使用队列来锁定,在队列中的session按照先进先出的原则,先进入队列的session先获得锁定。因此,当159号session释放锁定以后,131号 session会获得锁定,而135号session则被131号session阻塞。

 

如果159号session无法提交,则我们可以发出下面的语句直接删除159号session:

SQL> alter system kill session '150,6';

其中,150为session的SID号,而6为session的SERIAL#号。当删除159号session以后,该session所获得的锁定就会被pmon进程释放。

 

当我们更新某个表的记录,于是在表级别上添加RX模式的TM锁定。这时如果其他的用户要删除该表或修改该表结构时,则需要在表上添加X模式的TM锁定。由 于RX模式与X模式不兼容,则报错。如下所示:

SQL> drop table employees;

drop table employees

*

ERROR at line 1:

ORA-00054: resource busy and acquire with NOWAIT specified

 

从前面我们已经看到,一个事务不管更新多少条记录,都只能获得一个TX锁定。但是一个事务可以获得多个TM锁定。我们来看下面的例子:

SQL> select sid from v$mystat where rownum=1;

SID

----------

150

SQL> update employees set last_name=last_name||'a' where

department_id=60;

6 rows updated.

SQL> update departments set department_name='unknow' where

department_id=10;

1 row updated.

SQL> update locations set city='unknown' where location_id=1100;

1 row updated.

 

在同一个事务中,我们总共更新了3个表。然后检查v$lock视图:

SQL> select sid,type,id1,id2,decode(lmode,0,'None',1,'Null',2,

'Row share',

2         3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,

'Exclusive') lock_mode,

3         decode(request,0,'None',1,'Null',2,'Row share',

4         3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,

'Exclusive') request_mode,block

5  from v$lock

6  where sid=150;

SID   TYPE       ID1           ID2     LOCK_MODE       REQUEST_MODE    BLOCK

----  ----- -------- ----------  --------------     -------------   -------

150    TM        53777           0    Row Exclusive      None           0

150    TM        53772           0    Row Exclusive      None           0

150    TM        53767           0    Row Exclusive      None           0

150    TX       262185        5932    Exclusive          None           0

 

可以看到,我们获得了1个TX锁定及3个TM锁定。或者说,TX锁定与事务个数相同,而TM锁定则与被更新的表的个数相同。

在数据库系统中,我们同时可以获得的TX锁定的总个数由初始化参数transactions决定,而可以获得的TM锁定的个数则由初始化参数dml_locks决定。如下所 示:

SQL> select name,value from v$parameter where name in('transactions',

'dml_locks');

NAME                   VALUE

----------------    -------

dml_locks                748

transactions            187

 

默认情况下,同时可以启动187个事务,也就是获得187个TX锁定,以及获得748个TM锁定。也就是说,平均每个事务更新4个表(748/187=4)。我们还可以查 看v$resource_limit视图来了解这两个资源的使用情况:

SQL> select resource_name as "R_N",current_utilization as "C_U",max_

utilization as "M_U",

2  initial_allocation as "I_U" from v$resource_limit where resource

_name in('transactions ','dml_locks');

R_N                C_U          M_U          I_U

---------------  ---------- ---------- ----------

dml_locks          4            55          748

transactions       2            7          187

 

从结果中可以看到当前所使用的资源个数(C_U字段)、曾经达到过的同时被使用的资源的最大个数(M_U字段)、能够分配的资源的最大个数(I_U字段)。 这两个初始化参数修改以后,都必须重启实例使之生效。如果将初始化参数transactions设置为0,则重启以后,会发现初始化参数dml_locks也被自动设置 为0。但是我们查询视图v$resource_limit:

SQL> select resource_name as "R_N",current_utilization as "C_U",

max_utilization as "M_U",

2  initial_allocation as "I_U" from v$resource_limit where

resource_name in('transactions ','dml_locks');

R_N                C_U          M_U          I_U

---------------  ---------- ---------- ----------

dml_locks           0            0            0

transactions        0            5          187

 

输出结果说明,尽管我们将transactions设置为0,但是仍然可以发出事务操作语句,同时能够发出的最大事务个数仍然为默认的187个事务。而dml_locks的 资源个数则确实为0了,但这并不是说明不能对表添加表级锁,而只是说明不能进行大部分的DDL操作了。比如我们这时发出删除employees表的命令,如下所 示:

SQL> drop table employees;

drop table employees

*

ERROR at line 1:

ORA-00062: DML full-table lock cannot be acquired; DML_LOCKS is 0