代码之家  ›  专栏  ›  技术社区  ›  David R Tribble

原子独占SQL记录更新

  •  7
  • David R Tribble  · 技术社区  · 15 年前

    我想更新表中的一条记录,以反映给定的客户端会话已经在多会话环境中获取了该记录(现在拥有该记录以便进一步更新)。到目前为止,我已经做到了:

    create procedure AcquireRow(
      @itemNo   int,          -- Item ID to acquire
      @sessNo   int,          -- Session ID
      @res      char(1) out)  -- Result
    as
    begin
      -- Attempt to acquire the row
      update Items
        set
          State = 'A',      -- 'A'=Acquired
          SessionID = @sessNo
        where ItemID = @itemNo
          and State = 'N';  -- 'N'=Not acquired  
    
      -- Verify that the session actually acquired the row
      set @res = 'T';       -- 'T'=Success
      if @@rowcount = 0
        set @res = 'F';     -- 'F'=Failure
    end;
    

    @state 设置为 'T' 如果过程成功获取该行,则将其设置为 'F' 表示失败。

    这是否保证以原子方式工作,以便在多个会话调用时只有一个会话成功获取(更新)行 AcquireRow() rowlock ?


    根据Remus的回答,我将重新排列代码,如下所示:

    set @res = 'F';
    update ...;
    if @@rowcount > 0
        set @res = 'T';
    

    使用 output 子句或指定结果行的 ItemID update 也将是谨慎的。

    2 回复  |  直到 15 年前
        1
  •  9
  •   Remus Rusanu    15 年前

    @@ROWCOUNT 出了名的容易出错。例如,在你的情况下。从…起 MSDN :

    简单的语句 分配始终设置 @@行数 值设置为1。不会将任何行发送到 客户这些陈述的例子 是: SET @local_variable ...

    UPDATE . 使用行锁更安全,但不是必需的。即使优化器决定使用页面锁,“acquire”语义也是正确的。

    我可能更喜欢另一种方法,即使用 OUTPUT 更新 :

    declare @updated table (ItemId int);
    
    update Items
    set ...
    output inserted.ItemId
    into @updated (ItemId)
    where ...
    

    一般来说,为“acquire”使用real、committed和updates会遇到很多问题,因为您无法知道哪些行是真正获取的,哪些行是刚刚放弃的(客户端断开连接或崩溃,不发布“acquisition”)。

        2
  •  3
  •   womp    15 年前

    SQL server中的UPDATE语句在数据库引擎读取需要更新的行时获取更新锁,在写入时将转换为独占锁。

    当排他锁位于一行上时,所有其他事务都被阻止读取和写入它(除非读取事务处于读取未提交隔离状态,或者使用NOLOCK提示)。

    因此,是的,就目前而言,您的UPDATE语句是一个原子自动提交事务,因此对于同时调用它的多个会话来说,这应该是很好的。如果出于任何原因要将其分解为多个语句,则需要确保在SP中显式创建事务。