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類似,也是將進線程置於休眠狀態,可以通過參數指定具體的休眠時間(單位為秒)。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM