2008-11-03 115 views
4

想想看,我有一个交易:SQL Server锁定在这种情况下如何工作?

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 - 更新

假设执行前金额为0.

在这种情况下会发生什么情况下SQL Server的不同设置(读取未提交,读取已提交,可重复读取,可序列化),最后会有多少数量?是一个僵局?

+0

您的声明是反向的。 – 2008-11-03 15:47:24

+0

哈哈,科格斯将它同时编辑。 – 2008-11-03 18:27:54

回答

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 [email protected] = 0.00 
2 [email protected] = 0.00 
3 Deposits.Amount = 100.00 
4 Deposits.Amount = 100.00 

读未提交

1 [email protected] = 0.00 
2 [email protected] = 0.00 
3 Deposits.Amount = 100.00 
4 Deposits.Amount = 100.00 

重复读

1 [email protected] = 0.00 (locks out changes by others on Deposit.UserID = 123) 
2 [email protected] = 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 [email protected] = 0.00 (locks out changes by others on Deposit) 
2 [email protected] = 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 

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

提交读阅读UNCOMMITED,既不要锁被读取对其他用户修改数据。不同之处在于,未提交的读取将允许您查看尚未提交的数据(下侧),并且如果有其他数据被读取锁定(上行),这实际上是两次说同样的事情,则不会阻止读取。

可重复读取可序列化的,都表现得像阅读承诺阅读。对于锁定,锁定已被读取的数据以防其他用户修改。不同之处在于,可序列化的块比已读取的行更多,它也会阻止会引入之前不存在的记录的插入。

因此,通过重复读取,您可以在稍后的读取中看到新记录(称为:幻像记录)。在可序列化的情况下,您会阻止创建这些记录直到您提交。

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

0

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

未提交的读会导致这两个记录的值设定为100

读取已提交可能有点野趣的结果,这取决于两个线程的时间.. ..

这里是一个很好的文章中,我发现了大约Repeatable Read以及,提供了一个很好的例子

1

是的,你可能需要重复读取。

我可能会通过乐观锁定来处理这种情况,如果现有值与您读取(测试和设置)时的值相同,那么您只会更新。如果该值不相同,则引发错误。这使您可以运行未提交的,无死锁且无数据损坏的情况。

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 
2

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

所以我会附和不同的忠告......

为什么要用两个语句和类似下面的不只是一个说法?

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

此外,您的真实交易不仅仅是一个用户ID,对吧?如果不是这样,那么您将面临处理更多记录的风险。

1

否则,您可以使用锁定提示,以避免死锁(如果你有在读COMMITED模式服务器):

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 

在过程中一个语句的这种特定的程序(如凯文·费尔柴尔德张贴)为优选,没有按不会产生副作用,但在更复杂的情况下,UPDLOCK提示可能会变得方便。

相关问题