考虑我有一个交易:
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个线程上执行:
假设执行前Amount为0。
在这种情况下,在SQL Server的不同设置中会发生什么(读取uncommited,读取提交,可重复读取,可序列化),最终会有什么数量,会出现死锁吗?
答案 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的好文章,它提供了一个很好的例子