【问题标题】:About lock behavior when update row in sql server关于在sql server中更新行时的锁定行为
【发布时间】:2015-04-08 04:11:13
【问题描述】:

现在,我正在尝试使用用户提供的数字来增加 SQL Server 中的连续数字。

当多个用户以相同的数字同时插入一行时,我遇到了问题。

我尝试更新用户提供给临时表的数字,并且我希望当我以相同的条件更新同一个表时,SQL Server 将锁定对该行的任何修改,直到当前更新完成,但事实并非如此。

这是我使用的更新语句:

UPDATE GlobalParam 
SET ValueString = (CAST(ValueString as bigint) + 1) 
WHERE Id = 'xxxx'

你能告诉我有什么方法可以强制其他更新命令等到当前命令完成吗?

这是我的全部命令:

DECLARE @Result bigint;

UPDATE GlobalParam SET ValueString = (SELECT MAX(Code) FROM Item)

DECLARE @SelectTopStm nvarchar(MAX);
DECLARE @ExistRow int

SET @SelectTopStm = 'SELECT @ExistRow = 1 FROM (SELECT TOP 1 Code FROM Item WHERE Code = '999') temp'
EXEC sp_executesql @SelectTopStm, N'@ExistRow int output', @ExistRow output

IF (@ExistRow is not null)
   BEGIN
      DECLARE @MaxValue bigint

      DECLARE @ReturnUpdateTbl table (ValueString nvarchar(max));
      UPDATE GlobalParam SET ValueString = (CAST(ValueString as bigint) + 1)
      OUTPUT inserted.ValueString INTO @ReturnUpdateTbl 
      WHERE [Id] = '333A8E1F-16DD-E411-8280-D4BED9D726B3'
      SELECT  TOP 1 @MaxValue = CAST(ValueString as bigint) FROM @ReturnUpdateTbl
      SET @Result = @MaxValue
END
ELSE
   BEGIN
      SET @Output = 999 
   END
END

我把上面的代码写成一个存储过程。

这是我插入一项时的真实代码:

DECLARE @IncrementResult BIGINT

EXEC IncrementNumberUnique 
    , (some parameters)..
    ,@Result = @IncrementResult OUTPUT

INSERT INTO ITEM (Id, Code) VALUES ('xxxx', @IncrementResult)

我创建了 3 个线程并让它同时运行。

返回结果:

Id   Code
1    999
2    1000
3    1000

谢谢

【问题讨论】:

  • 当您尝试此更新时,带有Id = 'xxx' 的行实际上将在更新其值所需的时间内被独占锁定,然后该锁定将在事务提交(或滚动)时释放背部)。只有 一行 您正在更新 - 而不是整个表。因此,另一行的第二个 UPDATE 命令(具有不同的 Id 值)将能够继续 - 这是一件好事!
  • @marc_s:感谢您的回复。但是我有两个线程试图更新这一行(相同的 ID),我不知道为什么第二次更新不等待第一次完成。我已经尝试过 ROWLOCK 但我仍然得到重复的值
  • 只是这个“原子”UPDATE 强制第二个线程等待 - 但这真的是你的整个语句吗?会不会是你先有SELECT?那不会被锁定....
  • @marc_s :我发布了整个声明。你有什么想法吗?
  • 正如怀疑的那样 - 它不是单个原子 UPDATE _ 因此它很容易出现重复(如您所见)。那么为什么不让数据库使用INT IDENTITY 列来处理所有这些混乱的细节并完成它????

标签: sql sql-server locking increment


【解决方案1】:

如果我理解您的要求,请尝试ROWLOCK 提示,告诉优化器在更新需要时开始一一锁定行。

UPDATE GlobalParam WITH(ROWLOCK)
SET ValueString = (CAST(ValueString as bigint) + 1) 
WHERE Id = 'xxxx'

【讨论】:

  • 我已经尝试过使用 ROWLOCK,但我仍然得到了重复的值
【解决方案2】:

默认情况下,SQL Server 执行READ Committed 锁定,一旦提交读取操作,就会释放READ 锁定。一旦下面的Update 语句完成,所有读锁都会从表Item 中释放。

UPDATE GlobalParam SET ValueString = (SELECT MAX(Code) FROM Item)

由于您的INSERT into Item 超出了您的程序范围。您可以在SERIALIZABLE 隔离级别运行线程。像这样。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

DECLARE @IncrementResult BIGINT
EXEC IncrementNumberUnique 
, (some parameters)..
,@Result = @IncrementResult OUTPUT

INSERT INTO ITEM (Id, Code) VALUES ('xxxx', @IncrementResult)

将隔离级别更改为SERIALIZABLE 将增加item 表上的资源阻塞和争用。

了解更多隔离级别,请参考this

如果可能,您应该查看identity columns 并删除此类手动计算增量列。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-28
    • 2023-04-11
    • 1970-01-01
    • 1970-01-01
    • 2022-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多