ITPub博客

首页 > Linux操作系统 > Linux操作系统 > 锁--悲观锁定和乐观锁定

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

原创 Linux操作系统 作者:xw_z 时间:2009-07-15 09:45:25 0 删除 编辑


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/,如需转载,请注明出处,否则将追究法律责任。

下一篇: 锁--阻塞和死锁
请登录后发表评论 登录
全部评论

注册时间:2009-05-22

  • 博文量
    28
  • 访问量
    38249