• ORACLE HANDBOOK系列之十五:锁机制(Lock mechanism)


    锁机制的分类

    今天我们来了解Oracle中一项重要的机制,锁机制,它在允许最大并发性能的前提下保证数据的一致与完整。很多文章在说到锁机制时,往往写得特别复杂,在各种锁之外,又引入了所谓的”意向锁”等等,同时在该详细的地方,比如锁的兼容性方面,缺乏进一步的解释。所以我倾向”简单粗暴”风格,尽量把内容往简单的写。我们先来看看Oracle锁机制的基本分类。

    1)DML locks

    2)DDL locks

    3)Internal locks and latches,内部锁及闩,保护内部数据库结构

    4)Distributed locks,分布式锁

    5)PCM locks,并行高速缓存管理锁

    看起来很复杂的样子,不过我们今天的主要内容是DML locks,其他的暂时略过不表,DML locks又可以分成:

    1)TX锁,即事务锁(行级锁)

    2)TM锁,即表及锁

    对于TX锁,其实只有一个模式,即排他模式(exclusive,下称X锁),并不特别复杂,TM锁相对复杂一点。

    锁机制的基本示例

    SESS#132 SQL> insert into t_lock_1 values(1, 'a');
    1 row inserted
    
    SESS#132 SQL> select sid,type,id1,lmode, request from v$lock l where l.SID in (132, 202) and l.TYPE in ('TM','TX');
           SID TYPE        ID1      LMODE    REQUEST
    ---------- ---- ---------- ---------- ----------
           132 TM       109413          3          0
    
           132 TX       131075          6          0

    v$lock的lmode相应数值的含义是:

    0—none,

    1—null,

    2—row share (RS), 或sub share(SS),下称SS

    3—row exclusive (RX), 或sub exclusive(SX),下称SX

    4—share (S),

    5—share row exclusive (SRX), 或share sub exclusive (SSX),下称SSX

    6—exclusive (X). 

    Oracle在DML时自动获得的表级锁只有SX一种模式,其它的模式需要通过手工的Lock table才能获得;自动获得的事务锁也只有一种模式:X。

    另外可以看到,当我们插入一条数据(未提交)时,获得了两个锁,一个表级的SX,表明此表下的某些行正在被更改;另一个事务级的X,表明此数据行正在被我独占。

    SESS#132 SQL> insert into t_lock_1 values(2, 'b');
    1 row inserted
    SESS#132 SQL> select sid,type,id1,lmode,request from v$lock l where l.SID in (132, 202) and l.TYPE in ('TM','TX');
           SID TYPE        ID1      LMODE    REQUEST
    ---------- ---- ---------- ---------- ----------
           132 TM       109413          3          0
           132 TX       196633          6          0

    我们插入了第二行记录,表级锁数量未增加,因为是操作的是同一表; 事务锁数量也未增加,因为它们是在同一事务中。

    SESS#202 SQL> insert into t_lock_1 values(3, 'c');
    1 row inserted
    SESS#202 SQL> select sid,type,id1,lmode, request from v$lock l where l.SID in (132, 202) and l.TYPE in ('TM','TX');
           SID TYPE        ID1      LMODE    REQUEST
    ---------- ---- ---------- ---------- ----------
           202 TM       109413          3          0
           132 TM       109413          3          0
           202 TX       655370          6          0
           132 TX       196633          6          0

    在SESS#202中插入了第三条记录,这次我们看到了四条锁记录。同时,我们也看到了,表级的SX锁是相容的,这个下文会有解释。

    SESS#132 3:12:18 PM SQL> commit;
    Commit complete
    SESS#132 3:12:22 PM SQL> update t_lock_1 set val='aa' where id=1;
    1 row updated
    
    SESS#202 2:45:08 PM SQL> commit;
    Commit complete
    SESS#202 3:13:25 PM SQL> update t_lock_1 set val='aa' where id=1;

    更新同一条记录,此时SESS#202被阻塞

    SESS#132 3:15:05 PM SQL> select sid, type, id1,lmode, request from v$lock l where l.SID in (132, 202) and l.TYPE in ('TM','TX');
           SID TYPE        ID1      LMODE    REQUEST
    ---------- ---- ---------- ---------- ----------
           202 TX       524318          0          6
           132 TM       109413          3          0
           202 TM       109413          3          0
           132 TX       524318          6          0

    可以看见SESS#202成功获得了表级的SX,但在获得事务级锁时出现问题。这里我们看request列,request列即表示session希望获得的锁的类型,第一行中的6表示SESS#202希望获得事务级的X锁,而lmode字段的0则表示实际上未获得。

    SESS#132 3:18:59 PM SQL> select event, seconds_in_wait, sid from v$session_wait where sid in (132,202);
    EVENT                            SECONDS_IN_WAIT        SID
    -------------------------------- --------------- ----------
    SQL*Net message from client                    0        132
    enq: TX - row lock contention                325        202

    可以看到SESS#202的等待事件,enq即enquence,表示排队等待,TX – row lock contention表明在事务锁上存在争用。

    SESS#132 3:24:12 PM SQL> select s1.username || '@' || s1.machine || ' ( SID=' || s1.sid ||
           ' )  is blocking ' 
           || s2.username || '@' || s2.machine || ' ( SID=' || s2.sid || ' ) ' AS blocking_status
      from v$lock l1, v$session s1, v$lock l2, v$session s2
     where s1.sid = l1.sid
       and s2.sid = l2.sid
       and l1.BLOCK = 1
       and l2.request > 0
       and l1.id1 = l2.id1
       and l2.id2 = l2.id2;
    
    BLOCKING_STATUS
    --------------------------------------------------------------------------------
    SYSTEM@APAC\L00056378 ( SID=132 )  is blocking SYSTEM@APAC\L00056378 ( SID=202 )

    用上面的查询可以清楚地看到谁在阻塞谁

    SESS#132 3:25:35 PM SQL> commit;
    Commit complete
    
    SESS#202 3:25:40 PM SQL> commit;
    Commit complete

    上面列举的修改同一行数据造成的阻塞是我们最常见到的,还有一些情况也会导致阻塞,比如:SESS#1向表的主键列插入一值但未提交,SESS#2向此主键列插入相同值,则SESS#2被阻塞。有外键约束的表也存在类似的情况,即SESS#1向父表插入一值但未提交,SESS#2尝试引用此值,则SESS#2被阻塞。

    Select for update语句与锁

    SQL> select * from t_lock_1 for update;
    SQL> select sid,type,id1,lmode,request from v$lock l where l.SID =74 and l.TYPE in ('TM','TX');
           SID TYPE        ID1      LMODE    REQUEST
    ---------- ---- ---------- ---------- ----------
            74 TM       109413          3          0
            74 TX       458763          6          0

    很多文章中涉及select for update都表示这个语句会获得事务级的X锁,及表级的SS锁,后者其实是不正确的,从9.2.0.5以后,其在表级别上获得的就是SX锁,参见上面的查询。也就是说,它跟DML语句获得的锁是一样的。

    Lock table命令与锁

    到目前为止,我还很少在实际的应用开发中使用过Lock table,事实上,Oracle也不推荐对表的手工Lock,不过为了文章内容的完整,这里也简单做一些介绍。其中lock mode主要包括lmode中列出的几种。

    前面我们说到Oracle自动获取的表级锁的只有SX一种,但是使用Lock table语句则可以将表置于其它任意一种锁模式中。

    关于Lock table语句中的[nowait | wait n]选项,可以参见下面的示例,我们在SESS#1中将表lock,SESS#2尝试锁定同一表,在nowait的情况下,lock table语句立即出错,而在wait 10的情况下,语句等待了10秒中,之后才报错。

    SESS#1 4:55:54 PM SQL> lock table t_lock_1 in row exclusive mode;
    Table(s) locked
    
    SESS#2 4:56:04 PM SQL> lock table t_lock_1 in exclusive mode nowait;
    lock table t_lock_1 in exclusive mode nowait
    ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
    SESS#2 4:56:05 PM SQL>
    
    SESS#2 4:58:10 PM SQL> lock table t_lock_1 in exclusive mode wait 10;
    lock table t_lock_1 in exclusive mode wait 10
    ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
    SESS#2 4:58:21 PM SQL>

    谁占用了锁?

    通常,如果在发生锁的时候能得知是哪个用户造成的,无疑更有利于问题的解决。这时,需要借助v$session视图。

    SQL> select vl.SID,vl.ID1,vl.LMODE,vs.USERNAME, vs.OSUSER,vs.MACHINE,vs.PROGRAM,vs.PROCESS from v$lock vl join v$session vs on vl.SID=vs.SID and vl.type='TM';
           SID        ID1      LMODE USERNAME   OSUSER               MACHINE              PROGRAM              PROCESS
    ---------- ---------- ---------- ---------- -------------------- -------------------- -------------------- ------------------------
            15     109425          3 SYSTEM     APAC\Morven.Huang    APAC\L00056378       plsqldev.exe         7824:7828

    有了v$session视图中数据的帮助,我们就能确定具体是谁阻塞了大家。甚至于,借助v$session视图中的process字段,我们还能更详细地知道是哪个进/线程,以上述查询结果为例,如果你打开了多个plsqldev,可以借助process字段确定引发阻塞的语句是在哪个plsqldev进/线程中执行的。

    当然,我们也可以关联v$process视图(v$session.paddr =  v$process.addr)查看更加详细的进程信息(虽然它们并没有太大的用处)。

    另外,我们也可以具体查看是哪些对象被锁了,对于表级锁,v$lock中的字段ID1即object_id,我们可以关联系统字典dba_objects来得到对象信息。

    SQL> select vl.sid, vl.type, vl.id1,vl.lmode, do.object_name,do.object_type from v$lock vl join dba_objects do on vl.ID1=do.object_id and type='TM';
           SID TYPE        ID1      LMODE OBJECT_NAME     OBJECT_TYPE
    ---------- ---- ---------- ---------- --------------- -------------------
            15 TM       109425          3 T_LOCK_3        TABLE

    表级锁兼容性的解释

    解释一下,首先,我们说,就严格程度而言,X锁高于S锁,而对象锁又高于子对象锁,这两点应该没什么异议。

    前面说过,SS与SX中第一个字母是指的Sub,即子对象(我们可以把行理解成表的子对象),因此SS与SX级别最低,S次之,而SSX可以理解成S+SX,比S锁高,X则毫无疑问是最高的。这就是表格第一列从上到下的顺序了。

    再来看兼容性问题,SS,SX互相兼容,因为,无论SS,SX,实际上表示的是表中的一部分数据行被锁,如果其他用户请求表中另外的数据行,Oracle没有理由拒绝,从保证并发性能来讲,它们也必须兼容。有人要问:如果两个人申请锁定的是表相同的数据行怎么办?没关系,这里我们讨论的是表级锁,我们还有事务锁,由它来控制。

     SX与S不相容,S即Share,只有存在多人,才有所谓的共享,那么,多人同时查看一张表,如果其中一个人修数据或者表结构,他一定会被其他人鄙视的,所以这种情况下,SX与S不能兼容。那么,如果只有一个SESSION持有S锁,这个SESSION可以修改数据吗?答案是可以,因为只有一个SESSION持有锁的情况下,实际上也就无所谓”共享”了。

    SESS#136 12:55:05 PM SQL> select sid from v$session where audsid=userenv('SESSIONID');
           SID
    ----------
           136
    
    SESS#75 12:55:11 PM SQL> select sid from v$session where audsid=userenv('SESSIONID');
           SID
    ----------
            75
    
    SESS#136 12:55:46 PM SQL> lock table t_lock_4 in share mode;
    Table(s) locked
     
    SESS#75 12:56:13 PM SQL> update t_lock_4 set val='aa' where id=1;

    SESS#75中的update语句被阻塞。Ctrl+C中止SESS#75中的DML。

    SESS#136 12:57:56 PM SQL> select * from v$lock where sid=136 and type in ('TM','TX');
    ADDR     KADDR           SID TYPE        ID1        ID2      LMODE    REQUEST      CTIME      BLOCK
    -------- -------- ---------- ---- ---------- ---------- ---------- ---------- ---------- ----------
    0F346634 0F346664        136 TM       109427          0          4          0        143          0
    2A49D1E8 2A49D228        136 TX       262149      12143          6          0        143          0
    
    SESS#136 12:58:11 PM SQL> update t_lock_4 set val='aa' where id=1;
    1 row updated
    
    12:59:28 PM SQL> select * from v$lock where sid=136 and type in ('TM','TX');
    ADDR     KADDR           SID TYPE        ID1        ID2      LMODE    REQUEST      CTIME      BLOCK
    -------- -------- ---------- ---- ---------- ---------- ---------- ---------- ---------- ----------
    0F346634 0F346664        136 TM       109427          0          5          0          2          0
    2A49D1E8 2A49D228        136 TX       262149      12143      6          0        223          0

    此时,表级锁已经升级成5,即SSX

    DBMS_LOCK的使用

    DBMS_LOCK是Oracle提供给用户用于自定义锁的一个包,下面做一个示例,利用锁机机制模拟让两条语句同时执行。当然,DBMS_LOCK的本职用途是在开发时控制多个进线程间的同步,这跟C#中的锁机制是一样的。

    SESS#1

    declare
      v_lockhandle varchar2(200);
      v_result     number;
    begin
      dbms_lock.allocate_unique('control_lock', v_lockhandle);
      v_result := dbms_lock.request(v_lockhandle, dbms_lock.x_mode);
    end;

    SESS#2

    declare
      v_result     number;
      v_lockhandle varchar2(200);
    begin
      dbms_lock.allocate_unique('control_lock', v_lockhandle);
      v_result := dbms_lock.request(v_lockhandle, dbms_lock.ss_mode);
      insert into t_lock_5 values (2, systimestamp);
      commit;
    end;

    SESS#3

    declare
      v_result     number;
      v_lockhandle varchar2(200);
    begin
      dbms_lock.allocate_unique('control_lock', v_lockhandle);
      v_result := dbms_lock.request(v_lockhandle, dbms_lock.ss_mode);
      insert into t_lock_5 values (3, systimestamp);
      commit;
    end;

    此时SESS#2, SESS#3都被阻塞。

    SESS#1 (释放锁)

    declare
      v_lockhandle varchar2(200);
      v_result     number;
    begin
      dbms_lock.allocate_unique('control_lock', v_lockhandle);
      v_result := dbms_lock.release(v_lockhandle);
    end;

    查看表t_lock_5中的结果

    SQL> select * from t_lock_5;
           SID TS
    ---------- -------------------------------------------------
             2 20-SEP-12 04.16.59.133000 PM
             3 20-SEP-12 04.16.59.133000 PM

    示例很简单,DBMS_LOCK提供了申请锁(request)与释放锁(release)的方法,另外, allocate_unique提供辅助功能,用户可以用它将锁的名称(上面的“control_lock”)转换成lock handle,以便申请或释放的时候使用。另外,DBMS_LOCK包中还有一个sleep过程,与C#中Thread.sleep类似,也是将进线程置于休眠状态,可以通过参数指定具体的休眠时间(单位为秒)。

  • 相关阅读:
    在网页中实现截屏粘贴的功能
    CSS3 @font-face 做自定义图标
    Visual Studio报错一箩筐(持续更新)
    Axure实现vcg官网首页原型图
    Axure实现bootstrap首页线框图
    Web第一天——准备篇
    vue动态加载组件
    组件封装之将代码放到npm上
    node连接mysql生成接口,vue通过接口实现数据的增删改查(二)
    autoCAD2007 快捷键 标注
  • 原文地址:https://www.cnblogs.com/morvenhuang/p/2696867.html
Copyright © 2020-2023  润新知