2014-09-30 81 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