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编程艺术

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