2011-03-23 89 views
1

服務操作MyMethod(int id)從單個數據庫表中檢索特定行(基於參數id),並且在它返回之前也將該狀態保存回表格。如果兩個呼叫(第​​一個呼叫在交易T1內發生,而第二個在交易T2內)發生到MyMethod(),則服務將嘗試同時執行這兩個呼叫。由於T1T2嘗試訪問相同的數據庫表,兩個事務中的一個將被授予對該資源的訪問權限,而另一個應該被阻止,直到原始事務提交或中止。而是我得到一個異常事務(進程ID 54)已被死鎖的鎖資源與另一個進程,並已被選作死鎖犧牲品以下代碼不會導致死鎖,但我得到的事務(進程ID 54)已死鎖...

我不明白背後,因爲據我可以拋出異常僵局推理告訴我們沒有任何僵局的危險。首先,這兩個事務在不同的行上訪問和操作。爲什麼不是DB數據庫資源才鎖定,直到原始事務提交或中止?

下面是代碼:

[ServiceContract] 
public interface IService 
{ 
    [OperationContract] 
    [TransactionFlow(TransactionFlowOption.Allowed)] 
    void Process(int id); 
} 

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, IncludeExceptionDetailInFaults=true)] 
public class Service : IService 
{ 
    string state_Data = ""; 

    [OperationBehavior(TransactionScopeRequired = true)] 
    public void Process(int id) 
    { 
     GetState(id); 
     Thread.Sleep(6000); 
     SaveState(id); 
    } 


    private void GetState(int id) 
    { 
     using (SqlConnection con = new SqlConnection()) 
     { 
      con.ConnectionString = "data source=localhost; initial catalog=WCF; integrated security=sspi;"; 

      SqlCommand cmd = new SqlCommand(); 
      cmd.CommandText = "SELECT * FROM StateTable WHERE id = @id"; 
      cmd.Parameters.Add("@id", SqlDbType.Int).Value = id; 
      cmd.Connection = con; 
      con.Open(); 

      SqlDataReader reader = cmd.ExecuteReader(); 
      if (reader.Read()) 
       state_Data = reader["State"].ToString(); 
     } 
    } 

    private bool SaveState(int id) 
    { 
     using (SqlConnection con = new SqlConnection()) 
     { 
      con.ConnectionString = "data source = localhost; initial catalog=WCF; integrated security=sspi;"; 

      SqlCommand cmd = new SqlCommand(); 
      cmd.CommandText = "UPDATE StateTable SET [email protected] WHERE Id = @id"; 
      cmd.Parameters.Add("@id", SqlDbType.Int).Value = id; 
      cmd.Parameters.Add("@State", SqlDbType.NVarChar).Value = state_Data; 
      cmd.Connection = con; 
      con.Open(); 

      int ret = cmd.ExecuteNonQuery(); 
      return ret == 1; 
     } 
    } 
} 

編輯:

在情況下,這將有所幫助,這裏是客戶端代碼:

第一個客戶:

 ServiceClient proxy = new ServiceClient("WSDualHttpBinding_IService"); 

     using (TransactionScope scope = new TransactionScope()) 
     { 
      proxy.Process(1); 
      scope.Complete(); 
     } 

SECOND客戶:

 ServiceClient proxy = new ServiceClient("WSDualHttpBinding_IService"); 

     using (TransactionScope scope = new TransactionScope()) 
     { 
      proxy.Process(2); 
      scope.Complete(); 
     } 

謝謝

+0

對不起,我沒有注意到你的問題。我不知道任何有關索引,但我懷疑表有任何,因爲創建表的SQL語句沒有指定任何索引 - CREATE TABLE StateTable(id int PRIMARY KEY,狀態nvarchar(100)) – user437291 2011-03-23 21:24:18

回答

4

其實這個代碼是一個保證僵局。可以在同一個ID上調用任意數量的成功的GetState調用,所有調用都會成功,因爲它們都獲取(並保留,因爲可序列化的事務作用域)共享鎖,因此它們是兼容的。任何對SaveState的後續嘗試都將被阻止,因爲大量共享鎖都與更新所需的X鎖不兼容。接下來的SaveState將會陷入僵局。每次都有100%的保證。

如果您關心性能,則應該使用optimistic concurrency。如果性能不相關,那麼GetState應該專門鎖定狀態,例如。通過提供XLOCK提示。

當然,我認爲在StateTable的ID上有一個聚集索引。

+0

我幾乎不知道事務隔離,所以如果問題相當愚蠢,請原諒我。如果我正確理解你,這兩個事務接收鎖L1和L2,這使它們能夠一致地訪問(讀取)相同的DB表。並且當兩個事務嘗試同時調用SaveState時,它們都試圖獲取鎖X.那麼爲什麼SQL服務器不會「鎖定」X到兩個事務中的一個,並且當該事務釋放鎖X時,另一個事務可以獲取它?因此,我仍然不明白爲什麼這需要導致僵局?! – user437291 2011-03-23 21:15:10

+3

1)T1請求被授予S鎖。 2)T2請求S鎖,與T1保持的現有S鎖兼容,因此被授予。 3)T1請求X鎖。這與步驟2中授予T2的S鎖不兼容,因此它等待。 4)T2請求X鎖,它與步驟1中授予T1的S鎖不兼容,所以它等待。死鎖:T1從步驟3等待T2,T2從步驟4等待T1。 – 2011-03-23 21:20:19

+1

在步驟1)和2)中授予的S鎖在事務持續期間保持的事實是由OperationBehavior事務範圍引起的。沒有S鎖將立即釋放。這可以消除死鎖,但是你的應用程序會假設它讀取的狀態是最後一個狀態,T2會簡單地覆蓋T1更改(丟失更新)。 – 2011-03-23 21:26:44