【问题标题】:Do I need explicit locking when using atomic update使用原子更新时是否需要显式锁定
【发布时间】:2020-03-19 19:26:58
【问题描述】:

在我的 Web 应用程序中,我在数据库表中存储了一个计数器值,我需要在每个事务(高度并发)时递增或重置它。我是否需要显式锁定该行以避免丢失更新? 已提交读取事务隔离级别正在连接级别使用。以下语句更新计数器

UPDATE Counter c SET value =
  CASE
    WHEN c.last_updated = SYSDATE THEN c.value+1
    ELSE 1
  END,
  last_updated = SYSDATE
WHERE c.counter_id = 123;

据我所知,该语句是原子的,读取提交的隔离级别隐式锁定更新语句的行。在这种情况下,这是否会使显式锁定的使用变得多余?

【问题讨论】:

    标签: database oracle transactions locking acid


    【解决方案1】:

    您说的是乐观锁定与悲观锁定(“显式锁定”)。

    如果您使用悲观锁定,则可以保证不会丢失更新。然而,这种方法是有代价的:

    • 它不能很好地扩展 - 您实际上是在序列化对正在更新的行的访问,如果运行第一个事务的客户端由于某种原因挂起 - 每个人都被卡住了。
    • 鉴于通常多层 Web 应用程序的性质,它可能难以(或不可能)实施,因为显式锁定需要与更新本身在同一数据库连接中运行,您的中间层可能或可能无法保证。

    因此,您可以使用乐观锁定。假设如下表:

    create table t (key int, value int, version int);
    insert into t (1, 1, 1);
    

    基本上,逻辑是这样的(PL/SQL 代码):

     declare
        current_version t.version%type;
        current_value t.value%type;
        new_value t.value%type;
    begin 
    
        -- read current version of a row
        select version, value 
        into current_version, current_value 
        from t where id = 1;
    
        -- calculate new value; while we're doing this, 
        -- someone else may update the row, changing its version
        new_value = func_calculate_new_value(current_value);
    
        -- update the row...
        update t 
        set 
            value = new_value,
            version = version + 1
        where 1 = 1 
            and id = 1 
            -- but ONLY if the version of the row is the one we read
            -- otherwise there would be a lost update
            and version = current_version
        ;
    
        if sql%rowcount = 0 then
            -- 0 updated rows means the version is different 
            -- we're not updating because we don't want lost updates
            -- and we throw to let the caller know
            raise_application_error(-20000, 'Row version has changed');
            rollback;
        end if;
    end;
    

    【讨论】:

    • 为什么我需要使用原子更新锁定?不涉及隐式写锁吗?而且由于读取操作是更新语句的原子部分,所以访问是否在没有显式锁定的情况下被序列化?
    • @TuomasToivonen 是的,存在隐式行级锁,是的,(隐式)读取操作是更新语句的一部分。但是,除非您的更新非常简单,例如update t set c = c+1 where c2 = :x(这不需要您的应用程序知道您更新了什么,而只需更新已有的内容)——通常会涉及到显式的读取操作。并且该读取操作和更新本身之间存在差距,需要使用任何一种类型的锁来覆盖
    • @TuomasToivonen 如果您没有显式读取并且您运行的只是一次更新 - 这意味着您没有为并发进程提供执行丢失更新的机会。所以你很好。
    猜你喜欢
    • 1970-01-01
    • 2020-11-29
    • 1970-01-01
    • 2015-10-31
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多