2009-06-06 93 views
7

我在SQL Server 2008上的SELECT/UPDATE上遇到了死鎖問題。 我從這個線程讀取了答案:​​但我仍然不明白爲什麼我會死鎖。SELECT/UPDATE上的死鎖

我已經在下面的測試用例中重新創建了這種情況。

我有一個表:

CREATE TABLE [dbo].[SessionTest](
    [SessionId] UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL, 
    [ExpirationTime] DATETIME NOT NULL, 
    CONSTRAINT [PK_SessionTest] PRIMARY KEY CLUSTERED (
     [SessionId] ASC 
    ) WITH (
     PAD_INDEX = OFF, 
     STATISTICS_NORECOMPUTE = OFF, 
     IGNORE_DUP_KEY = OFF, 
     ALLOW_ROW_LOCKS = ON, 
     ALLOW_PAGE_LOCKS = ON 
    ) ON [PRIMARY] 
) ON [PRIMARY] 
GO 

ALTER TABLE [dbo].[SessionTest] 
    ADD CONSTRAINT [DF_SessionTest_SessionId] 
    DEFAULT (NEWID()) FOR [SessionId] 
GO 

我想首先選擇從這個表中的記錄,如果記錄存在設置過期時間爲當前時間加上一些間隔。它使用下面的代碼來實現:

protected Guid? GetSessionById(Guid sessionId, SqlConnection connection, SqlTransaction transaction) 
{ 
    Logger.LogInfo("Getting session by id"); 
    using (SqlCommand command = new SqlCommand()) 
    { 
     command.CommandText = "SELECT * FROM SessionTest WHERE SessionId = @SessionId"; 
     command.Connection = connection; 
     command.Transaction = transaction; 
     command.Parameters.Add(new SqlParameter("@SessionId", sessionId)); 

     using (SqlDataReader reader = command.ExecuteReader()) 
     { 
      if (reader.Read()) 
      { 
       Logger.LogInfo("Got it"); 
       return (Guid)reader["SessionId"]; 
      } 
      else 
      { 
       return null; 
      } 
     } 
    } 
} 

protected int UpdateSession(Guid sessionId, SqlConnection connection, SqlTransaction transaction) 
{ 
    Logger.LogInfo("Updating session"); 
    using (SqlCommand command = new SqlCommand()) 
    { 
     command.CommandText = "UPDATE SessionTest SET ExpirationTime = @ExpirationTime WHERE SessionId = @SessionId"; 
     command.Connection = connection; 
     command.Transaction = transaction; 
     command.Parameters.Add(new SqlParameter("@ExpirationTime", DateTime.Now.AddMinutes(20))); 
     command.Parameters.Add(new SqlParameter("@SessionId", sessionId)); 
     int result = command.ExecuteNonQuery(); 
     Logger.LogInfo("Updated"); 
     return result; 
    } 
} 

public void UpdateSessionTest(Guid sessionId) 
{ 
    using (SqlConnection connection = GetConnection()) 
    { 
     using (SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable)) 
     { 
      if (GetSessionById(sessionId, connection, transaction) != null) 
      { 
       Thread.Sleep(1000); 
       UpdateSession(sessionId, connection, transaction); 
      } 
      transaction.Commit(); 
     } 
    } 
} 

然後,如果我試圖從兩個線程中執行測試方法,他們試圖更新同一記錄我獲得以下的輸出:

[4] : Creating/updating session 
[3] : Creating/updating session 
[3] : Getting session by id 
[3] : Got it 
[4] : Getting session by id 
[4] : Got it 
[3] : Updating session 
[4] : Updating session 
[3] : Updated 
[4] : Exception: Transaction (Process ID 59) was deadlocked 
on lock resources with another process and has been 
chosen as the deadlock victim. Rerun the transaction. 

我無法理解它可能會發生使用可序列化隔離級別。我認爲首先選擇應鎖定行/表,不會讓另一個選擇獲取任何鎖。該示例是使用命令對象編寫的,但它僅用於測試目的。最初,我使用LINQ,但我想展示簡化的例子。 Sql Server Profiler顯示死鎖是鍵鎖。我將在幾分鐘內更新問題,並從sql server profiler中發佈圖表。任何幫助,將不勝感激。我明白這個問題的解決方案可能是在代碼中創建關鍵部分,但我試圖理解爲什麼可序列化隔離級別無法做到這一點。

這裏是死鎖圖形: deadlock http://img7.imageshack.us/img7/9970/deadlock.gif

在此先感謝。

+0

+1對於一個有據可查的問題! – 2009-06-06 11:44:16

回答

4

它不足以具有可序列化的事務,您需要提示鎖定以使其工作。

SERIALIZABLE隔離級別仍然通常會獲得「最弱」類型的鎖它能保證了串行化的條件被滿足(重複讀取,沒有幻像行等)

所以,你正在抓住一個共享鎖定的你的表在後面(在你的可串行化事務中)試圖升級到an update lock.如果另一個線程持有共享鎖(如果沒有其他主體持有共享鎖,它將工作),升級將失敗。

你可能想將其更改爲以下:

SELECT * FROM SessionTest with (updlock) WHERE SessionId = @SessionId 

這將確保一個更新鎖在執行SELECT獲取(這樣你就不會需要升級鎖)。

+0

它工作:)是否有可能實現這一點使用linq2sql? – empi 2009-06-06 11:53:10