2014-09-30 82 views
1

我正在多线程环境中对SQL事务进行一些测试。我正试图通过在一个循环中执行单个存储过程来生成死锁,这个过程由2个以并行方式运行的线程执行。我的两个线程的使用上开始同样的方法,连续执行一个存储过程:单存储过程和多线程的死锁

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

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

更新:继马克的建议,我想这个存储过程无济于事:

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 

从应该锁定在那些海誓山盟线程都相同常线程中运行它。我究竟做错了什么?

更新:上述过程确实会导致死锁,但是,至少需要3个线程才能完成此操作(不知道为什么,可能需要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隔离级别上。

+1

经典的死锁方式:使用“serializable”隔离级别,有spid A对某些数据(没有'updlock')执行读锁定;有spid B对相同的数据*进行读取锁定;现在如何spid A对同一数据进行写入锁定(尝试更改列),并让spid B尝试执行相同的操作。他们现在都在彼此之间僵持不下。 – 2014-09-30 07:31:07

+1

@Marc,我不假设简单的SELECT会在一行上得到一个读锁,对吗? – Alxg 2014-09-30 08:00:40

+0

在可序列化的隔离级别;是的:会的。在大多数其他隔离级别中:否 – 2014-09-30 08:01:00

回答

0

考虑在select中询问更新锁。 WITH(UPDLOCK)。

确保SELECT已经更新记录。

+0

“我试图产生死锁” - 这似乎是有用的确切*相反*,在实现这一目标方面 – 2014-09-30 08:19:11

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 
... 

编辑:

找到这似乎是相关的SO问题: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可能决定锁定整个表或至少锁定一个范围,以便只有一个锁被有效保存。因此,为了确保实际上在不同时间点获得两把锁,您可能实际上需要将两个选择分散到两个不同的表中。

+0

您的方法可以在所有隔离级别上工作吗? – Alxg 2014-09-30 22:55:14

+0

我认为它应该,如果你使用正确的类型的锁。 TomTom关于'WITH(UPDLOCK)'的建议显然是*写*锁定听起来很有希望。另一方面,如果您想要通过读写锁的组合来创建死锁情况,结果可能取决于隔离级别。 – JimmyB 2014-10-01 08:32:10

+0

是的,队友,这会产生死锁,然而,它使用UPDLOCK来明确建立所需的锁,并使用2个不同的事务来锁定彼此,这与我的问题所要求的有点不同。但感谢您获取更多信息。 – Alxg 2014-10-03 06:39:53