原子独占SQL记录更新

时间:2009-09-26 00:23:31

标签: sql sql-server tsql

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

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;

如果过程成功获取了行,则out变量@state设置为'T',否则设置为'F'以指示失败。

我的问题:这是否保证以原子方式工作,以便在多个会话同时调用AcquireRow()时,只有一个会话成功获取(更新)该行?或者有更好的方法吗?我需要一个明确的rowlock吗?

修订:
根据Remus的回答,我会重新安排代码:

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

使用output子句或将结果行的ItemID分配给update中的变量也是谨慎的。

2 个答案:

答案 0 :(得分:9)

众所周知,

@@ROWCOUNT很容易出错。例如,在你的情况下。来自MSDN

  

说明简单的陈述   赋值总是设置@@ROWCOUNT   值为1.没有行发送到   客户。这些陈述的例子   是:SET @local_variable ...

要正确,您应该在UPDATE之后立即检查@@ ROWCOUNT。使用ROWLOCK更安全,但不是必需的。即使优化器决定使用页锁,'acquire'语义也是正确的。

我可能更喜欢另一种方法,即使用OUTPUT的{​​{1}}子句:

UPDATE

这种方案更加防错,也更灵活,因为它允许获取未知的ItemId:获取的id放在@updated表变量中,并可以返回给调用者。

总的来说,对'获取'使用真实的,已提交的更新会遇到问题,因为您无法知道哪些行真正被获取以及哪些行被放弃(客户端断开连接或崩溃而不释放'获取' )。

答案 1 :(得分:3)

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

当一行上有一个独占锁时,所有其他事务都被阻止读取和写入(除非读取事务处于READ UNCOMMITTED隔离状态,或者使用了NOLOCK提示)。

所以是的,就目前而言,你的UPDATE语句是一个原子自动交换的事务,因此对于同时调用它的多个会话来说这应该没问题。如果您出于任何原因将其分成多个语句,则需要确保在SP中明确创建了一个事务。

Remus关于@@ ROWCOUNT的评论以及“获得”的一般用法是非常可靠的建议。