oracle處理丟失更新

關於鎖,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編程藝術

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章