关于锁,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编程艺术