創(chuàng)建自己的鎖定
?
???
對于鎖定來說,盡管其概念比較復(fù)雜,但是Oracle將這些復(fù)雜性都屏蔽在Oracle的內(nèi)核中。Oracle會自動完成鎖的管理,通常并不需要我們對鎖定進(jìn)行很多的管理工作。不僅如此,Oracle還提供了相關(guān)的工具包:dbms_lock,使得我們可以創(chuàng)建自己的鎖定。
?
??? 通過dbms_lock,我們可以控制對某個設(shè)備(比如打印機)的串行訪問,或者控制對某個文件的串行寫入等。比如通過我們自己的應(yīng)用程序打印數(shù)據(jù)時,要求在任意的時間點上,只能有一個用戶使用打印機。在具體實現(xiàn)時,我們可以創(chuàng)建一個打印機控制表。發(fā)出打印命令之前,先到該表里查詢打印標(biāo)記,如果已經(jīng)被設(shè)置為已打印,則說明其他用戶已經(jīng)開始打印了,必須等待。如果打印標(biāo)記還沒有被設(shè)置,則將打印標(biāo)記更新為已打印,然后打印。在實現(xiàn)過程中,還要考慮多個用戶進(jìn)程同時要求打印機時,所使用的排隊機制(通常都是先進(jìn)先出)。通過這種借助表的方式,我們可以實現(xiàn)對打印機的串行訪問。不過,這種方式要執(zhí)行SQL語句讀寫數(shù)據(jù)文件,相對性能較差。我們還可以有另一個選擇,就是調(diào)用dbms_lock創(chuàng)建自己的打印機鎖定。在打印前,獲得該打印機鎖定,打印以后釋放鎖定。在打印過程中,其他要獲得打印機的用戶進(jìn)程進(jìn)入隊列等待,并按照先進(jìn)先出的方式管理該隊列。
?
??? 我們以上面描述的打印機為例,來說明如何使用dbms_lock包。
?
???
分配打印機鎖定
?
??? 使用dbms_lock.allocate_unique存儲過程分配一個自定義的鎖定。如下所示:
?
SQL> create or replace function get_lockhandle return varchar2
? 2? is
? 3???? printer_lockname VARCHAR2(128) := 'printer_lock';
? 4???? printer_handle??? VARCHAR2(128);
? 5? begin
? 6???? if printer_handle is null then
? 7??????? dbms_lock.allocate_unique
? 8?????????? (lockname => printer_lockname,
? 9??????????? lockhandle => printer_handle);
?10???? end if;
?11???? return printer_handle;
?12? end;
?13? /
?
SQL> grant execute on get_lockhandle to public;
?
??? 我們在這里創(chuàng)建了一個函數(shù),該函數(shù)中創(chuàng)建了一個名為printer_lock的函數(shù)。在調(diào)用dbms_lock. allocate_unique過程時,會返回該鎖定的句柄(lockhandle)。
?
???
請求鎖定
?
??? 我們創(chuàng)建一個用于申請鎖定的存儲過程,如下所示:
?
SQL> create or replace procedure lock_printer
? 2???? (p_return_code out integer)
? 3? is
? 4???? v_temp_lockhandle varchar2(128);
? 5???? v_call_status????? integer;
? 6? begin
? 7???? v_temp_lockhandle := get_lockhandle;
? 8???? v_call_status := dbms_lock.request
? 9?????? (lockhandle => v_temp_lockhandle,
?10??????? lockmode??? => dbms_lock.x_mode,
?11??????? timeout???? => 5,
?12??????? release_on_commit => true);
?13???? p_return_code := v_call_status;
?14? end lock_printer;
?15? /
?
SQL> grant execute on lock_printer to public;
?
??? 在該存儲過程中,我們調(diào)用dbms_lock.request來申請打印機鎖定。在調(diào)用時,參數(shù)lockhandle為打印機鎖定的句柄;timeout表示請求鎖定時,如果5秒內(nèi)不能獲得,則放棄獲得,而進(jìn)入等待;release_on_commit表示發(fā)出commit或rollback命令時是否釋放鎖定。true為是,false為否;lockmode為打印模式,可選值如下表所示。
?
鎖定模式
|
描??? 述
|
對應(yīng)到TM鎖中的模式
|
nl_mode
|
null鎖定模式
|
Null
|
ss_mode
|
subshared鎖定模式
|
RS
|
sx_mode
|
subexclusive鎖定模式
|
RX
|
s_mode
|
shared鎖定模式
|
S
|
ssx_mode
|
subshared exclusive鎖定模式
|
RSX
|
x_mode
|
exclusive鎖定模式
|
X
|
?
??? 調(diào)用dbms_lock.request函數(shù)以后的返回值包括:
?
返回值
|
含義
|
0
|
申請鎖定成功
|
1
|
申請鎖定時超時
|
2
|
申請鎖定時發(fā)生死鎖
|
3
|
傳入?yún)?shù)錯誤
|
4
|
已經(jīng)獲得了鎖定,重復(fù)申請了鎖
|
5
|
傳入的鎖定句柄錯誤
|
?
?
???
釋放鎖定
?
??? 我們創(chuàng)建如下的存儲過程來釋放打印機鎖定,其中調(diào)用了dbms_lock.release存儲過程。
?
SQL> create or replace procedure release_printer
? 2? (p_return_code out integer)
? 3? is
? 4???? v_temp_lockhandle varchar2(128);
? 5???? v_call_status integer;
? 6? begin
? 7???? v_temp_lockhandle := get_lockhandle;
? 8???? v_call_status := dbms_lock.release
? 9?????? (lockhandle =>? v_temp_lockhandle);
?10???? p_return_code := v_call_status;
?11? end release_printer;
?12? /
?
SQL> grant execute on release_printer to public;
?
??? 調(diào)用dbms_lock.release函數(shù)以后的返回值包括:
?
返回值
|
含義
|
0
|
釋放鎖定成功
|
3
|
傳入?yún)?shù)錯誤
|
4
|
并沒有獲得要釋放的鎖定
|
5
|
傳入的鎖定句柄錯誤
|
?
??? 在創(chuàng)建了所有需要的存儲過程以后,我們來調(diào)用它們。打印前,執(zhí)行下面的語句:
?
SQL> select sid from v$mystat where rownum=1;
SID
----------
158
?
SQL> var v_out number
SQL> exec sys.lock_printer(:v_out);
?
SQL> print v_out
V_OUT
----------
0
?
??? 申請鎖定以后,傳出參數(shù)的值為0,說明申請鎖定成功。然后我們檢查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=158;
?
SID??? TYPE??? ID1??????????? ID2????? LOCK_MODE REQUEST_MODE? BLOCK
----? ----- ----------- --------- -------------- ------------- -------
158???? UL?? 1073742164??? ???? 0????? Exclusive???????? None?????? 0
?
?
??? 可以看到,獲得的鎖定類型為UL,表示用戶自定義的鎖定(User Lock)。只要是我們通過dbms_lock申請的鎖定,其type列都為UL;ID1表示鎖定的句柄;鎖定模式為排他鎖。根據(jù)ID1我們可以到dbms_lock_allocation視圖(該視圖顯示了已經(jīng)分配的用戶自定義的鎖定信息)中找到該鎖定。
?
SQL> select * from dbms_lock_allocated where lockid=1073742164;
?
NAME????????? LOCKID?????????? EXPIRATION
------------- ---------------- -------------------
printer_lock? 1073742164 ????? 2007-12-25 04:53:53
?
??? 然后我們再啟動一個session,并再次申請相同的打印機鎖定。
?
SQL> select sid from v$mystat where rownum=1;
SID
----------
159
?
SQL> var v_out number
SQL> exec sys.lock_printer(:v_out);
?
?
??? 在執(zhí)行l(wèi)ock_printer存儲過程時,會明顯感到延遲(其延遲時間長度就是我們前面在調(diào)用dbms_lock.request時指定的timeout參數(shù),這里就是5秒鐘)。在延遲過程中,我們查詢v$lock視圖,會發(fā)現(xiàn)下面的信息:
?
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$lock6? where sid=158;
?
?SID TYPE??? ????? ID1???? ID2LOCK_MODE? ??? REQUEST_MODE ?? BLOCK
---- ----- ----------- ------- -------------- ------------- -------
?158 UL? ?? 1073742164?????? 0 Exclusive ???? None??????????????? 1
?159 UL? ?? 1073742164?????? 0 None ????????? Exclusive?????????? 0
?
??? 很明顯,159號session在以Exclusive模式申請打印機鎖定時,由于158號session已經(jīng)持有該鎖定,因此發(fā)生等待。等待5秒鐘后,該存儲過程執(zhí)行完畢,我們顯示v_out參數(shù)值:
?
SQL> print v_out
V_OUT
----------
1
?
??? 返回值為1,說明申請鎖定時發(fā)生超時,因為其他進(jìn)程已經(jīng)獲得了該鎖定。
?
??? 從以上過程中,我們可以看到,通過使用dbms_lock,我們可以很方便地在自己的應(yīng)用程序中實現(xiàn)串行化控制。
?
?