關於鎖,thomas kyte 是這麼解釋的 鎖(lock)機制用於管理對共享資源的併發訪問,鎖主要是爲了在併發時保證事務的完整性,保護數據塊的結構不被破壞
在學習鎖定之前,先學習一下丟失更新:(模擬一個場景,假如此時數據庫沒有提供鎖)
(1) 會話Session1中的一個事務獲取(查詢)一行數據,放入本地內存,並顯示給一個最終用戶User1。
(2) 會話Session2中的另一個事務也獲取這一行,但是將數據顯示給另一個最終用戶User2。
(3) User1使用應用修改了這一行,讓應用更新數據庫並提交。會話Session1的事務現在已經執行。
(4) User2也修改這一行,讓應用更新數據庫並提交。會話Session2的事務現在已經執行。
有兩種方法可以防止丟失更新,即悲觀鎖定和樂觀鎖定。
悲觀鎖定:就是認爲事務悲觀,隨時都有可能改變,所以在user1在開始修改數據時就鎖定數據,防止其它user修改,直到它修改完才釋放鎖定。
oracle 中可以使用select for update 查詢時加上鎖,防止數據在本事務期間被修改。
我們使用Oracle scottt用戶的emp表完成悲觀鎖定,假如我們現在要給miller加薪,爲了防止丟失更新,我們使用悲觀鎖定。
SQL> select empno,ename,sal from emp where deptno=10;
EMPNO ENAME SAL
---------- ---------- ----------
7782 CLARK 2450
7839 KING 5000
7934 MILLER 1300
此時我們要鎖定Miller這一行,
SQL> select empno,ename,sal from emp where empno=7934 and ename='MILLER' and sal=1300 for update nowait;
EMPNO ENAME SAL
---------- ---------- ----------
7934 MILLER 1300
此時其它user修改這條數據時會得到一個ora-00054,只有等到當前用戶執行完,釋放了鎖定,下一個user才能使用。
所以我們現在可以放心的給miller加工資了
SQL> UPDATE emp set sal='2000' where empno='7934' and ename='MILLER';
1 row updated.
SQL> COMMIT;
Commit complete.
悲觀鎖定就是通過以上方式完成的,可以看到有效的防止了丟失更新,但是有可能會造成用戶等待的時間太長。
樂觀鎖定:就是比較樂觀,只在數據開始修改的加上所鎖定,但是這種有個問題,就是在修改前數據有可能已經被修改掉了,造成用戶修改失敗,或者修改的信息被覆蓋。
Oracle中樂觀鎖定的三種實現方式:更新前在應用中存儲所要操作行的“前映像”,更新時使用存儲的舊記錄來判斷當前值是否已經改變
- 使用一個特殊的列,這個列由一個數據庫觸發器或應用程序代碼維護,可以告訴我們記錄的 “版本”;
- 使用一個校驗和或散列值,這是使用原來的數據計算得出的;
- 使用新增的 Oracle 10g 特性 ORA_ROWSCN 。
1.使用版本列的樂觀鎖定 就是在表的原有基礎上增加一列版本列,一般是number或者date類型的,通過表上的一個行觸發器其維護。每次修改,這個觸發器遞增number或者修改date的值。
下面來使用scott.dept表的一個副本,並且我們使用觸發器來維護版本列
SQL> create table dept_copy
(deptno number(2),
dname varchar2(14),
loc varchar2(13),
mod_count number default 0,
constraint dept_pk primary key(deptno)
)
8 /
Table created.
SQL> insert into dept_copy (deptno,dname,loc)
2 select deptno,dname,loc from scott.dept;
4 rows created.
SQL> commit;
Commit complete.
現在我們增加觸發器。這兒我們需要一個前置觸發器
CREATE OR REPLACE TRIGGER mod_count_add
BEFORE update
ON scott.dept_copy
FOR EACH ROW
BEGIN
:new.mod_count := :old.mod_count+1;
END;
現在我們來看看錶數據
SQL> update dept_copy set loc='dallas' where deptno=20;
1 row updated.
SQL> commit;
Commit complete.
SQL> select * from dept_copy;
DEPTNO DNAME LOC MOD_COUNT
---------- -------------- ------------- ----------
10 ACCOUNTING NEW YORK 0
20 RESEARCH dallas 1
30 SALES CHICAGO 0
40 OPERATIONS BOSTON 0
在我執行了一條sql後mod_count變爲了1,那麼我們是如何使用這個版本列的了,在有版本列後,我們更新數據採用以下的方式
update dept_copy set loc='dallas' where deptno=20 and mod_count=1;
當數據已經被更新後,mod_count 就會改變,其它用戶在更新的話,就會更新不了
假如一個用戶做如下更新:
update dept_copy set loc='DA' where deptno=20 and mod_count=1;
此時其它用戶就會知道他已經被更新了.
2.使用校驗和的樂觀鎖定
這個其實和使用版本列沒有太大的區別,就是使用Oracle提供的hash算法來計算出一個虛擬的hash值,而這個值重複的可能性非常小,所以可以用來判斷數據是否被修改過。
我們使用Oracle提供的dbms_crypto.hash來計算數據唯一個hash值。在此之前我們先來學習一下這個生成hash值得存儲過程對於任意字符串,會生成唯一的hash值。有關hash函數的語法如下:
DBMS_CRYPTO.Hash (
src IN RAW,
typ IN PLS_INTEGER)
RETURN RAW;
DBMS_CRYPTO.Hash (
src IN BLOB,
typ IN PLS_INTEGER)
RETURN RAW;
DBMS_CRYPTO.Hash (
src IN CLOB CHARACTER SET ANY_CS,
typ IN PLS_INTEGER)
RETURN RAW;
SQL> select DBMS_CRYPTO.Hash('faabcdefdd',2) from dual;
DBMS_CRYPTO.HASH('FAABCDEFDD',2)
--------------------------------------------------------------------------------
45AB785F4A2F6C864A03357F553F6F2E
SQL> select DBMS_CRYPTO.Hash('haabcdefdd',2) from dual;
select DBMS_CRYPTO.Hash('haabcdefdd',2) from dual
*
ERROR at line 1:
ORA-01465: invalid hex number
此時必須使用utl_raw.cast_to_raw來預先轉換一下(不懂幹嘛的),後面單獨的學習一下。
SQL> select DBMS_CRYPTO.Hash(utl_raw.cast_to_raw('haabcdefdd'),2) from
dual; 2
DBMS_CRYPTO.HASH(UTL_RAW.CAST_TO_RAW('HAABCDEFDD'),2)
--------------------------------------------------------------------------------
9155B27846D5BA5EB83D64DDC8319277
那麼此時我們就用這個函數來生成表中數據的唯一hash值
begin
for x in (select deptno,dname,loc
from dept_copy
where deptno=10)
loop
dbms_output.put_line('Danme: ' || x.dname);
dbms_output.put_line('Loc: ' || x.loc);
dbms_output.put_line('Hash: ' ||
dbms_crypto.hash(
utl_raw.cast_to_raw(x.deptno ||'/' ||x.dname||'/'||x.loc),dbms_crypto.hash_sh1));
end loop;
end;
執行結果如下:
SQL> set serveroutput on;
SQL> /
Danme: ACCOUNTING
Loc: NEW YORK
Hash: C44F7052661CE945D385D5C3F911E70FA99407A6
現在我們要去更新這個值,我們會用當前字符串生成的hash值和他進行比較,如果符合則可以更新,負責說明數據已經被更新過了,我們使用以下存儲過程模擬演示更新的過程
begin
for x in (select deptno,dname,loc
from dept_copy
where deptno=10
for update nowait)
loop
if (hextoraw('C44F7052661CE945D385D5C3F911E70FA99407A6') <>
dbms_crypto.hash(
utl_raw.cast_to_raw(x.deptno ||'/' ||x.dname||'/'||x.loc),dbms_crypto.hash_sh1))
then
raise_application_error(-20001,'Row was modified');
end if;
end loop;
update dept_copy
set dname = lower(dname)
where deptno=10;
commit;
dbms_output.put_line('update success');
end;
更新後,重新查詢數據,並計算散列值,此時可以看到散列值大不相同。如果有人比我們先更新,散列值比較就會失敗。
Danme: accounting
Loc: NEW YORK
Hash: F3DE485922D44DF598C2CEBC34C27DD2216FB90F
3.使用ora_rowscn 的樂觀鎖定
oracle 10g開始,可以使用內置的ora_rowscn來實現樂觀鎖定,類似於版本列,但是是Oracle自己來維護。
ora_rowscn 建立在scn的基礎上,每次commit,scn會向前推進,更更新數據是要驗證scn未修改過。但是此時有個問題,那就是 ora_rowscn使進行塊級維護的,而默認情況下,一個塊有可能會有多條數據,所以當塊中的任意一條數據修改後ora_rowscn都會推進,造成修改數據的判斷不夠準確。oracle是通過如下的方式處理的
首先我們要重建表,但是會有一些不同。這兒我們使用一張新的表
create table dept_copy2
(deptno,dname,loc,data,
constraint dept_pk2 primary key(deptno)
)
rowdependencies
as
select deptno,dname,loc,rpad('*',3500,'*')
from scott.dept;
SQL> select deptno,dname,dbms_rowid.rowid_block_number(rowid) blockno , ora_rowscn from dept_copy2;
DEPTNO DNAME BLOCKNO ORA_ROWSCN
---------- -------------- ---------- ----------
10 ACCOUNTING 635 1464248
20 RESEARCH 635 1464248
30 SALES 636 1464248
40 OPERATIONS 636 1464248
SQL> update dept_copy2 set dname=upper(dname) where deptno=10;
1 row updated.
SQL> commit;
Commit complete.
SQL> select deptno,dname,dbms_rowid.rowid_block_number(rowid) blockno , ora_rowscn from dept_copy2;
DEPTNO DNAME BLOCKNO ORA_ROWSCN
---------- -------------- ---------- ----------
10 ACCOUNTING 635 1464456
20 RESEARCH 635 1464248
30 SALES 636 1464248
40 OPERATIONS 636 1464248
此時應該明白和版本列沒有什麼不同了。
scn如何轉換爲牆上的時鐘
select deptno ,scn_to_timestamp(ora_rowscn) ts from dept_copy2;
SQL> select deptno,ora_rowscn,scn_to_timestamp(ora_rowscn) ts from dept_copy2;
DEPTNO ORA_ROWSCN
---------- ----------
TS
---------------------------------------------------------------------------
10 1464456
29-JUL-13 06.11.40.000000000 PM
20 1464248
29-JUL-13 06.07.34.000000000 PM
30 1464248
29-JUL-13 06.07.34.000000000 PM
DEPTNO ORA_ROWSCN
---------- ----------
TS
---------------------------------------------------------------------------
40 1464248
29-JUL-13 06.07.34.000000000 PM
SQL> select deptno ,ora_rowscn,scn_to_timestamp(ora_rowscn) ts from dept_copy2;
DEPTNO ORA_ROWSCN
---------- ----------
TS
---------------------------------------------------------------------------
10 1464456
29-JUL-13 06.11.40.000000000 PM
20 1464248
29-JUL-13 06.07.34.000000000 PM
30 1464248
29-JUL-13 06.07.34.000000000 PM
DEPTNO ORA_ROWSCN
---------- ----------
TS
---------------------------------------------------------------------------
40 1464248
29-JUL-13 06.07.34.000000000 PM
SQL> select deptno ,scn_to_timestamp(ora_rowscn) ts from dept_copy2;
DEPTNO
----------
TS
---------------------------------------------------------------------------
10
29-JUL-13 06.11.40.000000000 PM
20
29-JUL-13 06.07.34.000000000 PM
30
29-JUL-13 06.07.34.000000000 PM
DEPTNO
----------
TS
---------------------------------------------------------------------------
40
29-JUL-13 06.07.34.000000000 PM
參照thomas kyte oracle 9i&10g編程藝術