锁--悲观锁定和乐观锁定


1.什么是锁

  • 锁机制用于管理对共享资源的并发访问。例如,执行一个存储过程,过程本身会以某种模式锁定,以允许其他用户执行这个过程,但是不允许另外的用户以任何方式修改这个过程。数据库中使用锁是为了支持对共享资源进行并发访问,与此同时还能提供数据完整性和一致性。


2.锁定问题

  • 丢失更新

1> 例:

1session1的一个事务查询一行数据展现给user1

2)另一个session2的一个事务也查询同一行数据展现给user2

3)然后user1通过应用程序更新并提交这行数据,他完成了整个事务。

4User2也同样通过应用程序更新并提交这行数据,他也完成了整个事务。

上面的过程就会造成“更新丢失”,因为所有在第三步修改的数据全部都会丢失。一个典型的例子就是售票系统,比如一个用户(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)使用版本列

对每个要保护的表增加一列(一般是NUMBERDATE/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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值