SQL Server锁定如何在这种情况下工作?

时间:2008-11-03 15:43:25

标签: sql sql-server locking

考虑我有一个交易:

BEGIN TRANSACTION
DECLARE MONEY @amount
SELECT Amount AS @amount
  FROM Deposits
  WHERE UserId = 123
UPDATE Deposits
  SET Amount = @amount + 100.0
  WHERE UserId = 123
COMMIT

它按顺序在2个线程上执行:

  1. 主题1 - 选择
  2. 主题2 - 选择
  3. 主题1 - 更新
  4. 主题2 - 更新
  5. 假设执行前Amount为0。

    在这种情况下,在SQL Server的不同设置中会发生什么(读取uncommited,读取提交,可重复读取,可序列化),最终会有什么数量,会出现死锁吗?

5 个答案:

答案 0 :(得分:2)

其他人已经解决了使用REPEATABLE READ的问题。

所以我会提出不同的建议......

为什么要使用两个语句而不只是一个语句,如下所示?

UPDATE Deposits
SET Amount = Amount + 100.0
WHERE UserId = 123

此外,您的真实交易不仅仅是用户ID,对吧?如果没有,您将面临使用比原先预期更多记录的风险。

答案 1 :(得分:2)

很好的陈述方案。我决定测试它。

这是我的设置脚本:

CREATE TABLE Deposits(Amount Money, UserID int)
INSERT INTO Deposits (Amount, UserID)
SELECT 0.0, 123
--Reset
UPDATE Deposits
SET Amount = 0.00
WHERE UserID = 123

这是我的测试脚本。

SET TRANSACTION ISOLATION LEVEL Serializable
----------------------------------------
-- Part 1
----------------------------------------
BEGIN TRANSACTION
DECLARE @amount MONEY
SET @amount =
(
SELECT Amount
FROM Deposits
WHERE UserId = 123
)
SELECT @amount as Amount
----------------------------------------
-- Part 2
----------------------------------------
DECLARE @amount MONEY
SET @amount =  *value from step 1*
UPDATE Deposits
SET Amount = @amount + 100.0
WHERE UserId = 123
COMMIT
SELECT *
FROM Deposits
WHERE UserID = 123

我在两个查询分析器窗口中加载了这个测试脚本,并按照问题的描述运行每个部分。

所有读取都在任何写入之前发生,因此所有线程/场景都会将值0读入@amount。

结果如下:

读取已提交的

1 T1.@Amount = 0.00
2 T1.@Amount = 0.00
3 Deposits.Amount = 100.00
4 Deposits.Amount = 100.00

阅读未提交的

1 T1.@Amount = 0.00
2 T1.@Amount = 0.00
3 Deposits.Amount = 100.00
4 Deposits.Amount = 100.00

可重复阅读

1 T1.@Amount = 0.00 (locks out changes by others on Deposit.UserID = 123)
2 T1.@Amount = 0.00 (locks out changes by others on Deposit.UserID = 123)
3 Hangs until step 4. (due to lock in step 2)
4 Deadlock!
Final result: Deposits.Amount = 100.00

序列化

1 T1.@Amount = 0.00 (locks out changes by others on Deposit)
2 T1.@Amount = 0.00 (locks out changes by others on Deposit)
3 Hangs until step 4. (due to lock in step 2)
4 Deadlock!
Final result: Deposits.Amount = 100.00

以下是每种类型的解释,可用于通过思维模拟来达到这些结果。

读取提交读取未提及,两者都不会锁定因其他用户修改而读取的数据。不同之处在于,未提交读取将允许您查看尚未提交的数据(下行),并且如果其他人阻止读取数据(上行),则不会阻止您的读取,这实际上是两次相同的事情。

可重复读取可序列化,两者的行为类似于读取提交。对于锁定,两者都锁定已被读取而不被其他用户修改的数据。区别在于可序列化的块比已读取的行多,它还会阻止插入会引入之前不存在的记录。

因此,通过可重复读取,您可以在以后的读取中看到新记录(称为:幻像记录)。使用serializable,您可以阻止创建这些记录,直到您提交为止。

以上解释来自我对这篇msdn文章的解释。

答案 2 :(得分:1)

是的,你可能想要重复阅读。

我可能通过乐观锁定来处理这个问题,其中只有现有值与读取时(测试和设置)相同时才更新。如果值不相同,则引发错误。这允许您运行read-uncommitted,没有死锁,并且没有数据损坏。

BEGIN TRANSACTION
DECLARE MONEY @amount
SELECT Amount AS @amount
  FROM Deposits
  WHERE UserId = 123
UPDATE Deposits
  SET Amount = @amount + 100.0
  WHERE UserId = 123 AND Amount = @amount
IF @@ROWCOUNT <> 1 BEGIN ROLLBACK; RAISERROR(...) END
ELSE COMMIT END

答案 3 :(得分:1)

否则,您可以使用锁定提示来避免死锁(如果您的服务器处于读取提交模式):

BEGIN TRANSACTION
DECLARE MONEY @amount
SELECT Amount AS @amount
  FROM Deposits WITH(UPDLOCK)
  WHERE UserId = 123
UPDATE Deposits
  SET Amount = @amount + 100.0
  WHERE UserId = 123
COMMIT

在这个特定的过程中,单个语句(如Kevin Fairchild发布)是首选,不会引起副作用,但在更复杂的情况下,UPDLOCK提示可能会变得很方便。

答案 4 :(得分:0)

我相信你会想要使用可重复读取,这将锁定记录,第一个选择将获得值,然后它将更新阻塞线程2直到它完成。因此,您的示例中的最终结果为

未提交读取会导致两个记录都将值设置为100。

Read committed可能会有一些有趣的结果,具体取决于两个线程的时间....

这是我发现的一篇关于Repeatable Read的好文章,它提供了一个很好的例子