`

Oracle 锁与锁争用

 
阅读更多

大多数的锁都是行级锁,它们防止两个会话修改相同的行。事务的ACID(原子性、一致性、隔离性和持久性)属性,特别是一致性,它要求Oracle限制对表数据的并发修改。锁(lock)机制用于管理对共享资源的并发访问,Oracle也会在其他多个级别上使用锁,从对多种不同的资源提供并发访问。例如过程,当一个过程正在运行时,过程本身会以某种模式锁定,它允许其他的用户执行这个过程,但不允许其他的用户以任何的方式修改该过程。

 

Oracle使用两个字符的编码来对锁进行分类。我们可以通过查询v$lock_type视图来进行查看,如下:

SQL> column type format a4

SQL> column name format a20

SQL> column description format a40

SQL> select type,name,description

  2  from v$lock_type

  3  order by type

  4  /

 

TYPE NAME                 DESCRIPTION

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

SH   Active Session Histo To prevent multiple concurrent flushers

     ry Flushing

 

SI   Streams Table Instan Prevents muiltiple streams table instant

     tiation              iations

 

SJ   KTSJ Slave Task Canc Serializes cancelling task executed by s

     el                   lave process

 

SK   Shrink Segment       Serialize shrink of a segment

SL   Serialize Lock reque Request serialization to LCK0

...............................................................................................

 

Oracle大约有200多种类型的锁,大多数的锁都是内部的而且也很少被看到。我们经常遇到的是TM(DML)和TX(事务)锁,当针对于一个对象的DML执行时,经常同时会看到TX和TM锁的组合。下面,我们来看一下这两个锁在v$lock_type视图中的简单描述:

SQL> select type,name,description

  2  from v$lock_type

  3  where type in ('TX','TM')

  4  /

 

TYPE NAME                 DESCRIPTION

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

TM   DML                  Synchronizes accesses to an object

TX   Transaction          Lock held by a transaction to allow other 

                                    transactions to wait for it

锁可以被多种模式所持有,首先,锁能以独占或共享的模式被持有;共享模式的锁阻止相同类型独占模式的锁,但不排斥其他类型的共享模式的锁。因为,打算防止任意其他的会话得到一个独占模式的锁,我们可以取得一个共享模式的锁。Oracle锁的模式有如下一些类型:

NULL:无锁。SS或RS:共享行级锁。SX或RX:独立占行级锁。S:共享表级锁。X:独占表级锁。SSX或SRX:表级共享行级独占锁。

 

下面,我们以例子的形式说明。

我们首先来看一下更新表的一行记录时,该行所持有的锁:

SQL> create table t

  2  as

  3  select *

  4  from all_objects

  5  /

 

Table created.

 

SQL> alter table t add constraint t_pk primary key(object_id)

  2  /

 

Table altered.

 

SQL> update t set created = sysdate

  2  where object_id = 369

  3  /

 

1 row updated.

 

SQL> column type format a4

SQL> column name format a16

SQL> column table_name format a10

SQL> select type,name,id1,id2,lmode,

  2         decode(type,'TM',(select object_name

  3                            from dba_objects

  4                           where object_id = id1)) table_name

  5  from v$lock join v$lock_type using(type)

  6  where sid = (select sid

  7                from v$session

  8               where audsid = userenv('sessionid'))

  9  /

 

TYPE NAME                    ID1        ID2      LMODE TABLE_NAME

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

AE   Edition Lock            100          0          4

TM   DML                   15514          0          3 T

TX   Transaction          589830       1011          6

说明?当我们对表一行进行修改进时,会看到这些类型的类型的锁。AE锁在11g中引入,是一个版本锁,它是基于版本重定义的一部分,ID1是SID当前使用的版本的对象ID,这个版本锁可以防止引用版本作出修改。

对于锁的模式,我们用如下的SQL进行查看:

SQL> column object_owner format a10

SQL> column object_name format a10

SQL> column locked_mode format a15

SQL> select b.session_id as sid,

  2         a.owner as object_owner,

  3         a.object_name,

  4         decode(b.locked_mode, 0, 'None',

  5                               1, 'Null (NULL)',

  6                               2, 'Row-S (SS)',

  7                               3, 'Row-X (SX)',

  8                               4, 'Share (S)',

  9                               5, 'S/Row-X (SSX)',

 10                               6, 'Exclusive (X)',

 11                               b.locked_mode) locked_mode

 12  from   dba_objects a,

 13         v$locked_object b

 14  where  a.object_id = b.object_id

 15  /

 

       SID OBJECT_OWN OBJECT_NAM LOCKED_MODE

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

       236 DJP01      T          Row-X (SX)

 

说明:该表所使用的锁模式为独占行级锁。这种锁防止其他任意的会话在同一时间更新相同的行。

如果需要的话,我们也可以使用lock table来锁整个表,如下:

SQL> lock table t in exclusive mode;

 

Table(s) Locked.

 

SQL> select b.session_id as sid,

  2         a.owner as object_owner,

  3         a.object_name,

  4         decode(b.locked_mode, 0, 'None',

  5                               1, 'Null (NULL)',

  6                               2, 'Row-S (SS)',

  7                               3, 'Row-X (SX)',

  8                               4, 'Share (S)',

  9                               5, 'S/Row-X (SSX)',

 10                               6, 'Exclusive (X)',

 11                               b.locked_mode) locked_mode

 12  from   dba_objects a,

 13         v$locked_object b

 14  where  a.object_id = b.object_id

 15  /

 

       SID OBJECT_OWN OBJECT_NAM LOCKED_MODE

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

       236 DJP01      T          Exclusive (X)

 

SQL>

给表T加独占表级锁之后,我们在另一个会话中对表T进行一下操作,如下:

SQL> truncate table t;

truncate table t

               *

ERROR at line 1:

ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源, 或者超时失效

 

 

SQL> drop table t;

drop table t

           *

ERROR at line 1:

ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源, 或者超时失效

当对其进行修改时,它会出现ORA-00054错误,对其进行DML操作时,会处在挂起状态。

下面, 我们重点看一下TX与TM锁。

当事务发起第一个修改时,会得到TX(事务锁)锁。而且会一直持有这个锁,直到事务发生commit或rollback为止。TX锁用作一种排除机制,使得其他会话可以等待这个事务的执行。我们可在v$event_name中查看该等待事件,在下面,我会用实例的形式进行介绍。Oracle的锁定过程可以概括为如下一些过程:

(1)找到想要锁定那一行的地址

(2)到达那一行。

(3)锁定这一行。

在Oracle中对数据行进行锁定时,行指向事务ID的一个副本,事务ID存储在包含数据的块中,释放锁时,事务ID会保留下来。这个事务ID是事务所独有的,表示了撒销段号,事务槽和序号。事务ID留在包含数据行的块上,可以告诉其他会话,你拥有这个数据(I并非块上的所有数据都是你的,只是你修改的那一行归你所有)。另一个会话到来时,它会看到锁ID,由于锁ID表示一个事务,所以可以很快地查看持有这个锁的事务是否是活动的。如果锁不是活动的,则允许访问这个数据,如果是活动的,会话就会要求一但释放锁将会得到通知。

下面,我们来看几个例子,我首先创建如下示例表:

SQL> create table emp

  2  as

  3  select rownum empno,initcap(dbms_random.string('l',5)) ename,

  4         round(dbms_random.value(3000,10000),2) sal,

  5         trunc(dbms_random.value(1,10)) deptno

  6  from dual

  7  connect by rownum <= 30

  8  /

 

Table created.

 

SQL> alter table emp add constraint emp_pk primary key(empno)

  2  /

 

Table altered.

 

SQL> create index deptno_idx on emp(deptno)

  2  /

 

Index created.

 

SQL> create table dept

  2  as

  3  select rownum deptno,dbms_random.string('u',6) dname, 

  4            dbms_random.string('u',10) loc

  5  from dual

  6  connect by rownum <= 10

  7  /

 

Table created.

 

SQL> alter table dept add constraint dept_pk primary key(deptno)

  2  /

 

Table altered.

 

SQL> alter table emp add constraint deptno_fk foreign key(deptno)

  2     references dept(deptno)

  3  /

 

Table altered.

 

SQL> exec dbms_stats.gather_table_stats(user,'emp',cascade=>true)

 

PL/SQL procedure successfully completed.

 

SQL> exec dbms_stats.gather_table_stats(user,'dept',cascade=>true)

 

PL/SQL procedure successfully completed.

下面,我进行一个更改,并查看相应的v$视图。

SQL> update dept set dname = lower(dname)

  2  /

 

10 rows updated.

 

SQL> select xidusn,xidslot,xidsqn

  2  from v$transaction

  3  /

 

    XIDUSN    XIDSLOT     XIDSQN

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

         3         26       1016

说明:通过查询v$transaction 视图,我们得到了事务ID,撒销段号,事务槽,序号分别为3,26,1016。

下面, 我们来查看该事务的锁,(我在TOM书中,看到如下的一个计算,可以得到事务ID)。

SQL> column username format a10

SQL> column lock_mode format a10

SQL> select username,l.sid,trunc(id1/power(2,16)) xidusn,

  2         bitand(id1,to_number('ffff','xxxx'))+0 xidslot,

  3         id2 xidsqn,request,

  4         decode(lmode,0,'None',

  5                      1,'NULL',

  6                      2,'SS',

  7                      3,'SX',

  8                      4,'S',

  9                      5,'SSX',

 10                      6,'X',lmode) lock_mode

 11  from v$lock l,v$session s

 12  where l.type = 'TX'

 13    and s.username = user

 14    and l.sid = s.sid

 15  /

 

USERNAME          SID     XIDUSN    XIDSLOT     XIDSQN    REQUEST LOCK_MODE

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

DJP01             703          3         26       1016          0 X

说明:通过上述的一个巧妙计算,得到了事务ID。它持有了一个排他锁。请求(request)为0,表示没有发出请求,也就是说,你拥有这个锁。

下面,我在不同的会话中进行一个更改,并进行上述的查询:

Session 1:

SQL> update emp set ename = upper(ename)

  2  /

 

30 rows updated.

 

Session 2:

SQL> update dept set deptno = deptno -10;

(处于挂起状态)。

在Session 1中进行上述的查看:

SQL> select username,l.sid,trunc(id1/power(2,16)) xidusn,

  2         bitand(id1,to_number('ffff','xxxx'))+0 xidslot,

  3         id2 xidsqn,request,

  4         decode(lmode,0,'None',

  5                      1,'NULL',

  6                      2,'SS',

  7                      3,'SX',

  8                      4,'S',

  9                      5,'SSX',

 10                      6,'X',lmode) lock_mode

 11  from v$lock l,v$session s

 12  where l.type = 'TX'

 13    and s.username = user

 14    and l.sid = s.sid

 15  /

 

USERNAME          SID     XIDUSN    XIDSLOT     XIDSQN    REQUEST LOCK_MODE

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

DJP01               7          3         26       1016          6 None

DJP01             703          3         26       1016          0 X

 

SQL> select xidusn,xidslot,xidsqn

  2  from v$transaction

  3  /

 

    XIDUSN    XIDSLOT     XIDSQN

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

         3         26       1016

说明:在上述的查询中,出现了两个会话,会话7的request为6,表示它对排他锁进行了一个请求。会话703阻塞了会话7。我们也可以使用一个自连接进会话阻塞的查看,如下:

SQL> column blocker format a10

SQL> column blockee format a10

SQL> select (select username from v$session where sid = a.sid) blocker,a.sid,

  2        ' is blocking ',

  3        (select username from v$session where sid = b.sid) blockee,b.sid

  4  from v$lock a,v$lock b

  5  where a.block = 1

  6    and b.request > 0

  7    and a.id1 = b.id1

  8    and a.id2 = b.id2

  9  /

 

BLOCKER           SID 'ISBLOCKING'               BLOCKEE           SID

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

DJP01             703  is blocking               DJP01               7

当我们使用dbms_metadata.get_ddl进行对表或索引的元数据进行查看时,会看到initrans,maxtrans参数。这些参数是创建表时或索引时,不可缺少的参数,我们通常习惯上不去手工进行指定,都采用的是的默认的值。在段中每个块都有一个块的首部,这个块的首部有一个事务表,事务表中建立了一些条目用于描述那些事务将块上的那些行或元素进行锁定。那么,这个事务表的大小由于参数initrans指定,默认为2。事务表会根据需要动态扩展,最大可以达到maxtrans参数所指定的值。在这里要注意一下,在10g以上的版本中,maxtrans参数将被忽略,其值总是为255。在TOM的书上有一个很好的例子,下面,我们来看一下:

SQL> create table t

  2  (

  3  x number primary key,

  4  y varchar2(4000)

  5  )

  6  /

 

Table created.

 

SQL> insert into t(x,y)

  2  select rownum,rpad('*',148,'*')

  3  from dual

  4  connect by rownum <= 46

  5  /

 

46 rows created.

 

SQL> commit;

 

Commit complete.

 

SQL> select length(y),dbms_rowid.rowid_block_number(rowid) blk,

  2         count(*),max(x),min(x)

  3  from t

  4  group by length(y),dbms_rowid.rowid_block_number(rowid)

  5  /

 

 LENGTH(Y)        BLK   COUNT(*)     MAX(X)     MIN(X)

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

       148        135         46         46          1

说明:这里的目的是让这些行都在同一个块上,现在,我们需要查看多个事务试图同时锁住这个块上的数据时,会发生什么。为此,我们创建如下过程:

SQL> create or replace procedure do_update(p_n in number)

  2  as

  3     pragma autonomous_transaction;

  4     l_t_rec t%rowtype;

  5     resource_busy exception;

  6     pragma exception_init(resource_busy,-54);

  7  begin

  8     select * into l_t_rec

  9      from t

 10     where x = p_n

 11       for update nowait;

 12

 13     do_update(p_n + 1);

 14

 15     commit;

 16  exception

 17     when resource_busy

 18     then

 19        dbms_output.put_line('locked out trying to select row ' || p_n);

 20        commit;

 21     when no_data_found

 22     then

 23        dbms_output.put_line('finished');

 24        commit;

 25  end do_update;

 26  /

 

Procedure created.

说明:这里,采用自治事务,目标是使用同一个会话达到事务的并发,这样可以避免运行多个会话。这里使用主键锁定表行,主键值从1开始,如果过程得到这一行锁而不必等待,它将把主键值增加为1,并使用递归再次执行,第二次调用,会锁定第二行,第二次调用会锁定第三行,依次类推,执行下去。如果过程需要等待,它会出现,ora-00054的错误,并输出“ocked out trying to select row  <primary_key_valu>“。这说明在处理完要加锁的行之前,我们已经用完了这个块上的事务槽。另一方面,如果发现没有行需要加锁,这说明,我们对这个块上的每一行都已经加锁,也就是说,块首部的事务表能够增长到足够大来记录事务。

下面,我们进行一个调用:

SQL> set serveroutput on

SQL> exec do_update(1)

locked out trying to select row 38

 

PL/SQL procedure successfully completed.

说明:从这个输出发现,我们可以锁定到37行,当锁定38行的时候,事务槽已经用完。从而可以说明:该块上允许最大并发事务为37。如果并发事务过多,将等待事务表。

TM锁用于确保在修改表内容时,表的结构不会发生改变。例如下面的这个情况:

Session 1:

SQL> update t set y = null;

 

46 rows updated.

 

SQL>

Session 2:

SQL> drop table t;

drop table t purge

           *

ERROR at line 1:

ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源, 或者超时失效

当对表进行DML操作时,都会加TM锁。在11g中,通过设置ddl_lock_timeout参数,单位为秒,可以让其进行一个等待,而不是立即出错,当超出这个时间时,才返回错误。例子如下:

Session 1:

SQL> update t set y = null;

 

46 rows updated.

Session 2:

SQL> alter session set ddl_lock_timeout = 60

  2  /

 

Session altered.

 

SQL> drop table t;

这时,它没有立即返回错误,而是处在一个挂起状态。在这期间,如果Session 1对事务进行了commit或rollback,则Session 2中的相应的语句被执行。

 

参考至:http://blog.163.com/donfang_jianping/blog/static/13647395120133401920519/

如有错误,欢迎指正

邮箱:czmcj@163.com

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics