锁
1.什么是锁
锁机制用于管理对共享资源的并发访问。例如,执行一个存储过程,过程本身会以某种模式锁定,以允许其他用户执行这个过程,但是不允许另外的用户以任何方式修改这个过程。数据库中使用锁是为了支持对共享资源进行并发访问,与此同时还能提供数据完整性和一致性。
2.锁定问题
丢失更新
1> 例:
1)session1的一个事务查询一行数据展现给user1。
2)另一个session2的一个事务也查询同一行数据展现给user2。
3)然后user1通过应用程序更新并提交这行数据,他完成了整个事务。
4)User2也同样通过应用程序更新并提交这行数据,他也完成了整个事务。
上面的过程就会造成“更新丢失”,因为所有在第三步修改的数据全部都会丢失。一个典型的例子就是售票系统,比如一个用户(user1)在网上预定查询到1号位的票 还没售出,同时另一用户(user2)在现场售票点查询也查到1号位票没售出。然后 user1预订了这张票(即售票系统更新了数据库表中1号位的信息“已预订”),而这时 user2又将这张票卖给了现场购票的人(即user2也成功更新 1号位的信息“已售”,覆盖更新了user1的更新),等到user1去拿票的时候他预定的票却已经被卖出去了,这就是应用系统出现的一个严重的问题。
悲观锁定
1> 定义:在试图更新之前我们就把行锁住了,因为我们很悲观,对于这一行能不能保持未改变很是怀疑。
2> 适用环境:悲观锁定仅用于有状态或有连接环境,即:你的应用与数据库有一条连续的连接,而且至少在事务生存期中只有你一个人使用这条连接。
3> 例:
1)首先用户查询数据:
select empno,ename,sal from emp where deptno = 10;
EMPNO ENAME SAL
---------- ---------- ----------
7782 CLARK 2450
7839 KING 5000
7934 MILLER 1300
2)然后用户选择他想更新的一行,假设用户选择更新MILLER行,在这个时间点上,应用会绑定用户选择的值,从而查询数据库,并确保数据尚未修改
variable empno number
variable ename varchar2(20)
variable sal number
exec :empno := 7934; :ename := 'MILLER'; :sal := 1300;
3)在查询中,除了验证数据尚未修改外(通过绑定变量),还要使用FOR UPDATE NOWAIT锁定这一行。
select empno,ename,sal from emp
where empno = :empno
and ename = :ename
and sal = :sal
for update nowait;
分析:1)如果底层数据没有改变,就会再次得到MILLER行,而且这一行会被锁定,不允许其他会话更新(但是允许其他会话读)。
2)如果另一个用户正在更新这一行,我们就会得到一个ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired。相应的,必须等待更新这一行的用户执行工作。
3)在选择数据和指定有意更新之间,如果有人已经修改了这一行,我们就会得到0行。此时,应用需要重新查询,并在允许在最终用户修改之前锁定数据。
乐观锁定
1> 定义:把所有锁定都延迟到即将执行更新之前才做。我们很乐观,认为数据不会被其他用户修改。因此,会等到最后一刻才去看我们的想法对不对。
2> 实现乐观并发控制的方法:
1)应用本身存储行的所有“前”映像。
2)使用版本列
对每个要保护的表增加一列(一般是NUMBER或DATE/TIMESTAMP列),通常通过表上的一个行触发器来维护。每次修改行时,这个触发器要负责递增NUMBER列中的值,或者更新DATE/TIMESTAMP列。
原理:如果应用要实现乐观并发控制,只需保存这个附加列的值,而不需保存其他列的所有“前”映像。应用只需验证请求更新那一刻,数据库中这一列与最初读出的指是否匹配。如果两个值相等,就说明这一行未被更新过。
例:
1> 重建DEPT表,并附加一个LAST_MOD列,默认值是当前系统时间
create table dept
(deptno number(2)
,dname varchar2(14)
,loc varchar2(13)
,last_mod timestamp with time zone
default systimestamp
not null
,constraint dept_nk primary key(deptno)
)
/
insert into dept(deptno,dname,loc)
select deptno,dname,loc
from scott.dept;
2> 保存LAST_MOD列的值
variable deptno number
variable dname varchar2(14)
variable loc varchar2(13)
variable last_mod varchar2(50)
begin
:deptno := 10;
select dname,loc,last_mod
into :dname,:loc,:last_mod
from dept
where deptno = :deptno;
end;
3> 根据LAST_MOD列的匹配结果,尝试更新:若匹配,则更新一行,否则更新0行。
update dept
set dname = initcap(:dname),
last_mod =systimestamp
where deptno = :deptno
and last_mod = to_timestamp_tz(:last_mod)
/
3)使用校验和或散列值
原理:与版本列方法类似,不过在此将基数据本身散列为一个“虚拟的”版本列。通过比较散列值(即“虚拟的”版本列),尝试更新。
例:使用Oracle 10g Release 1中提供的DBMS_CRYPTO.HASH包来计算基数据本身的散列值
begin
for x in (select deptno,dname,loc
from dept
where deptno = 10)
loop
dbms_output.put_line('Dname: ' || 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;
/
Dname: Accounting
Loc: NEW YORK
Hash: D12772FDFCDFEBE37AB8CEF4D0F38AD39A35044D
注:DBMS_CRYPTO的返回值是一个RAW变量,显示时,会隐式地转换为HEX,所以在更新语句中进行比较前,应使用函数hextoraw将所显示的值从HEX转换为RAW。
优缺点:缺点:计算散列或校验和是一个CPU密集型操作,其计算代价昂贵。如果CPU是稀有资源,在这种系统上就必须充分考虑这一点;优点:从“网络友好性”角度看,该方法只需在网络上传输相当小的散列值,而不是行的完整的前映像和后映像,所以消耗的资源会小得多。
4)使用ORA_ROWSCN
原理:与使用版本列类似,只是不需要在表中增加额外的列,也不需要额外的更新/维护代码来更新这个值,全由Oracle自动执行。
ORA_ROWSCN建立在内部Oracle系统时钟(SCN)基础上。在Oracle中,每次提交时,SCN都会推进,更新数据时要验证SCN未修改过。
除非你创建表时支持在行级维护ORA_ROWSCN,否则Oracle会在块级维护。即:默认情况下,一个块上的多行会共享相同的ORA_ROWSCN ,如果更新一个块上的某一行,而且这个块上还有另外50行,那么这些行的ORA_ROWSCN也会推进。(这会导致假警报,你认为某一行已经修改,但实际上它并没有改动。)
例:创建表DEPT
create table dept
(deptno,dname,loc,data
,constraint dept_nk primary key(deptno)
)
as
select deptno,dname,loc,rpad('*',3500,'*')
from scott.dept
show parameter db_block_size
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_block_size integer 8192
由于我使用的块大小是8KB,而一行的宽度大约为3550字节,所以每个块上将有两行。(原因:在一个块中装入两行后,剩余的空间不足以再装入一行,此时会发生行迁移(Oracle在决定行连接之间先试图进行行迁移 )
select deptno,dname,
dbms_rowid.rowid_block_number(rowid) blockno,
ora_rowscn
from dept
DEPTNO DNAME BLOCKNO ORA_ROWSCN
---------- -------------- ---------- ----------
10 ACCOUNTING 87370 896968
20 RESEARCH 87370 896968
30 SALES 87371 896968
40 OPERATIONS 87371 896968
确实每块有两行。更新DEPTNO=10的一行
update dept
set dname = lower(dname)
where deptno = 10;
commit;
再查看ORA_ROWSCN
DEPTNO DNAME BLOCKNO ORA_ROWSCN
---------- -------------- ------------------------------------ ----------
10 accounting 87370 897077
20 RESEARCH 87370 897077
30 SALES 87371 896968
40 OPERATIONS 87371 896968
可以看出,虽然我们只修改了一行,但是块87370上两行的ORA_ROWSC N值都推进了。因此,我们必须启用ROWDEPENDENCIES再重建这个段,以修改这种默认行为。启用该特性会为每行增加6字节的开销,因此需要重新创建表,而不能只是简单的ALTER TABLE:必须修改物理块结构来适应这个特性。
重建表:
drop table dept;
create table dept
(deptno,dname,loc,data
,constraint dept_nk primary key(deptno)
)
ROWDEPENDENCIES
as
select deptno,dname,loc,rpad('*',3500,'*')
from scott.dept
查看ORA_ROWSCN
DEPTNO DNAME BLOCKNO ORA_ROWSCN
---------- -------------- ---------- ----------
10 ACCOUNTING 87370 904325
20 RESEARCH 87370 904325
30 SALES 87371 904325
40 OPERATIONS 87371 904325
更新DEPTNO=10的一行,再查看ORA_ROWSCN
DEPTNO DNAME BLOCKNO ORA_ROWSCN
---------- -------------- ---------- ----------
10 accounting 87370 904491
20 RESEARCH 87370 904325
30 SALES 87371 904325
40 OPERATIONS 87371 904325
可以看出,现在只有DEPTNO=10这一行的ORA_ROWSCN改变,所以现在可以依靠ORA_ROWSCN来为我们检测行级修改了。
将SCN转换为墙上时钟时间
使用透明ORA_ROWSCN列还有一个好处:可以把SCN转换为近似的墙上时钟时间(有+/-3秒的偏差),从而发现最后一次修改发生在什么时间。
select deptno,ora_rowscn,scn_to_timestamp(ora_rowscn) ts
from dept;
DEPTNO ORA_ROWSCN TS
---------- ---------- ---------------------------------------------------------------------------
10 904491 14-JUL-09 02.30.58.000000000 PM
20 904325 14-JUL-09 02.28.16.000000000 PM
30 904325 14-JUL-09 02.28.16.000000000 PM
40 904325 14-JUL-09 02.28.16.000000000 PM
乐观锁定还是悲观锁定?
1> 使用悲观锁定的前提是它需要与数据库有一条有状态的连接,如客户/服务器连接,因为无法跨越连接持有锁。正式因为这一点,在当前许多情况下,悲观锁定不太现实。
2> 如今对大多数应用来说,都建议采用乐观并发控制。在实现乐观并发控制的方法中,建议采用版本列方法,并增加一个时间戳列(时间戳列能提供一个额外的信息:这一行最后一次更新发生在什么时间?);如果想对一个已经使用悲观锁定机制的表再增加乐观并发控制,建议采用ORA_ROWSCN方法(因为现有的应用可能不希望出现一个新列);散列/校验和方法在数据库独立性方面很不错。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/17014649/viewspace-609190/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/17014649/viewspace-609190/