单个存储过程和多个线程的死锁

时间:2014-09-30 07:27:33

标签: c# sql sql-server multithreading stored-procedures

我正在多线程环境中对SQL事务运行一些测试。我试图通过在paralel中运行的2个线程的循环中执行单个存储过程来生成死锁。我的两个线程在启动时使用相同的方法,它连续执行一个存储过程:

using (TestDataContext db = new TestDataContext())
{
    while (true)
    {
        db.DeadLocking();
    }
}

有人可以给出一个“DeadLocking”存储过程的例子,它可以在这种情况下可靠地生成死锁。它必须使用交易(单个或多个)。我已经研究了很多,并且看到了很多关于如何在sql中生成死锁的例子,但是,它们都没有在我的代码中工作。请帮忙。

更新:遵循Marc的建议我尝试了这个sproc无济于事:

CREATE PROCEDURE [dbo].[DeadLocking]
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
SET NOCOUNT ON
        BEGIN TRANSACTION
            DECLARE @val varchar(1)
            SELECT @val = Record FROM Test.dbo.Records WHERE RecordId = 1
            UPDATE Test.dbo.Records SET Record = @val WHERE RecordId = 1
        COMMIT TRANSACTION
END

从paralel中的两个线程运行它应该将这些线程锁定在彼此之上。我做错了什么?

更新:上面的过程确实会导致死锁,但是,至少需要3个线程来执行此操作而不是2(不知道为什么,可能需要2但仍需要永久)。有趣的是,这也会造成僵局:

CREATE PROCEDURE [dbo].[DeadLocking]
AS
BEGIN
    SET NOCOUNT ON
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    UPDATE Test.dbo.Records SET Record = 1 WHERE RecordId = 1
END

我猜这是因为存储过程本身在场景后面实现了某种事务逻辑。如果有人有更多关于它为什么会发生的信息,请分享。 请注意,死锁仅在UPDATE发生,并且不会发生在SELECT上。这种情况发生在SERIALIZABLE和REPEATABLE READ隔离级别上。

2 个答案:

答案 0 :(得分:0)

考虑在select中要求更新锁。 WITH(UPDLOCK)。

确保SELECT已经更新记录。

答案 1 :(得分:0)

要创建死锁,您肯定需要两个不同的过程,或至少需要以不同顺序获取锁的执行分支。

事实上,确保获取锁定的严格命令是防止死锁的众所周知的方法。

因此,您将需要两个不同的锁A和B,例如在两个不同的表上。然后一个线程将尝试锁定A 然后 B,而另一个线程将尝试锁定B 然后 A.只有这样才有机会创建一个死锁。

如果您想增加在运行时实际发生死锁的可能性,则需要一些延迟,例如:

lock A
delay5Seconds
lock B
delay5Seconds
unlock B
unlock A

lock B
delay5Seconds
lock A
delay5Seconds
unlock A
unlock B

这允许其他线程在正确的时间点到达死锁。

为了确保每次都产生死锁,必须创建一个机制来同步两个线程执行,比如

lock A
wait for thread #2 to lock B
lock B
...

lock B
wait for thread #1 to lock A
lock A
...

编辑:

发现这个似乎有关的问题:Confused about UPDLOCK, HOLDLOCK

从中推断你可以尝试这样:

BEGIN TRANSACTION
SELECT * FROM Test.dbo.Records WHERE RecordId = 1 WITH (UPDLOCK, HOLDLOCK)
WAITFOR DELAY '00:00:10'
SELECT * FROM Test.dbo.Records WHERE RecordId = 999999 WITH (UPDLOCK, HOLDLOCK)
WAITFOR DELAY '00:00:10'
COMMIT TRANSACTION

BEGIN TRANSACTION
SELECT * FROM Test.dbo.Records WHERE RecordId = 999999 WITH (UPDLOCK, HOLDLOCK)
WAITFOR DELAY '00:00:10'
SELECT * FROM Test.dbo.Records WHERE RecordId = 1 WITH (UPDLOCK, HOLDLOCK)
WAITFOR DELAY '00:00:10'
COMMIT TRANSACTION

原则上,这应该在所有事务隔离级别上执行。 (您在两个线程中都使用显式锁,因此DBMS会忽略它。) 但是,如果来自同一个表的两个连续选择实际上将获得两个单独的锁(每个受影响的记录一个),或者如果DBMS可能只是决定锁定整个表,或者至少它的单一范围,因此只有一个锁被有效地保持。因此,为了确保您在不同时间点实际获得两个锁定,您实际上可能希望将两个选择分散到两个不同的表。