2012-04-19 66 views
3

我遇到分佈式事務問題。SQL Server鎖問題(使用WCF的分佈式事務)

我正在使用SQL Server 2008 R2,Windows 7,.Net 4.0。

這是我真正想做的事:

  • 我有一個緩衝數據庫(稱爲A)
  • 我還有一個數據庫(稱爲B)
  • 我想從數據庫中的數據發送到通過web服務WCF(HTTP上的SOAP,A和B不相同的機器上)
  • 數據庫B如果數據被成功地發送到數據庫B,數據是從數據庫甲
除去

請注意,這是一個非常簡單我現在遇到的問題。在我的真實應用程序中,我沒有在多個SQL Server實例之間複製數據(請參閱文章末尾的更新)。

一切都是事務的一部分,所以整個操作是原子的。

這裏就是我目前正在按順序做(在交易中的一切):

  1. 我從數據庫中的讀取塊(比如50行)
  2. 我更新在步驟1中讀取的行(我實際上設置了布爾值Sent列的值爲true,所以在將來我知道這些行被髮送)
  3. 我使用(客戶端)作爲事務一部分的WCF Web服務(SOAP 1.2,wsHttpBinding)流量(阻塞同步呼叫)。如果從服務器接收到的無例外的web服務請求發送(包含在web服務請求數據)在步驟1中
  4. web服務實現(服務器側)讀出的數據在數據庫中插入B數據
    • ,與Sent值作爲true數據從數據庫甲
    • 除去如果特定FaultException從服務器接收到的,與所述Sent值作爲真數據是從數據庫甲
    • 除去用於其它FaultExceptions和其他異常(端點不foun d,或其他任何東西),與Senttrue數據不會從數據庫中刪除一個

注:實際上有多個緩衝數據庫(A1,A2,A3,......,AN)和多個目標數據庫(B1,.... BN)。有N個線程來處理N個數據庫。

如果我運行我的服務器和我的客戶端,一切都工作得很好。數據從數據庫A到數據庫B以塊爲單位「原子」傳輸。 當我在事務中間(處理中止)時殘酷地停止我的客戶機時,大多數時候一切正常(即,數據不會從數據庫A中刪除,也不會添加到數據庫B中)。

但有時(這是我的實際問題),我的數據庫B被鎖定。解鎖它的唯一選擇是終止服務器進程。

當問題發生時,我可以在MSDTC中看到存在活動事務(請注意,截圖中有3個事務,因爲我目前正在處理3個緩衝區數據庫和3個目標數據庫,但有時存在即使有3個DB也只有1個交易)。

enter image description here

我看到嘗試運行SELECT againt數據庫B.正如你可以看到下面的圖片時數據庫B被鎖定,我SELECT請求由會話ID 67對應於INSERT受阻數據庫B由服務器進程。

enter image description here

鎖保持永遠(沒有事務超時,即使經過1+小時),直到服務器處理終止。我無法驗證或取消MSDTC中的交易,我收到警告說「it cannot be aborted because it is not "In Doubt"」。

爲什麼數據庫B保持鎖定狀態?如果客戶端被終止,那麼事務不應該失敗,並且在某個超時後釋放數據庫B的鎖定?

這裏是我的服務器端代碼:

// Service interface 
[ServiceContract] 
public interface IService 
{ 
    [OperationContract] 
    [FaultContract(typeof(MyClass))] 
    [TransactionFlow(TransactionFlowOption.Allowed)] 
    void SendData(DataClass data); 
} 

// Service implementation 
[ServiceBehavior()] 
public partial class Service : IService 
{ 
    [OperationBehavior(TransactionScopeRequired = true)] 
    public void SendData(DataClass data) 
    { 
     if (data == null) 
     { 
      throw new FaultException<MyClass>(new MyClass()); 
     } 

     try 
     { 
      // Inserts data in database B 
      using (DBContextManagement ctx = new DBContextManagement()) 
      { 
       // Inserts data using Entity Framework 
       // This will add some entities to the context 
       // then call context.SaveChanges() 
       ctx.InsertData(data); 
      } 
     } 
     catch (Exception ex) 
     { 
      throw new FaultException<MyClass>(new MyClass()); 
     } 
    } 
} 

這裏是我的服務器端配置(自託管的WCF服務):

<system.serviceModel> 
    <services> 
     <service name="XXXX.MyService"> 
     <endpoint binding="wsHttpBinding" bindingConfiguration="WsHttpBinding_IMyService" contract="XXXX.IMyService" /> 
     </service> 
    </services> 
    <bindings> 
     <wsHttpBinding> 
     <binding name="WsHttpBinding_IMyService" transactionFlow="true" allowCookies="true" > 
      <readerQuotas maxDepth="32" maxArrayLength="2147483647" maxStringContentLength="2147483647" /> 
      <security mode="None" /> 
     </binding> 
     </wsHttpBinding> 
    </bindings> 
    <behaviors> 
     <serviceBehaviors> 
     <behavior name=""> 
      <serviceTimeouts transactionTimeout="00:00:20" /> 
      <serviceMetadata httpGetEnabled="true" /> 
      <serviceDebug includeExceptionDetailInFaults="true" /> 
      <dataContractSerializer maxItemsInObjectGraph="2147483647" /> 
     </behavior> 
     </serviceBehaviors> 
    </behaviors> 
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" /> 
    </system.serviceModel> 

這裏是我的客戶端代碼:

try 
{ 
    using (DBContextManagement ctx = new DBContextManagement()) 
    { 
     using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted, Timeout = new TimeSpan(0, 0, 30) }, EnterpriseServicesInteropOption.None)) 
     { 
      // First of all, retrieves data from database A 
      // Internally queries the database A through Entity Framework 
      var data = ctx.GetData(); 

      // Mark data as sent to the server 
      // Internally updates the database A through Entity Framework 
      // This actually set the Sent property as true, then call 
      // context.SaveChanges() 
      ctx.SetDataAsSent(data); 

      try 
      { 
       // Send data to the server 
       MyServiceClient proxy = new MyServiceClient(); 
       MyServiceClient.SendData(data); 

       // If we're here, then data has successfully been sent 
       // This internally removes sent data (i.e. data with 
       // property Sent as true) from database A through entity framework 
       // (entities removed then context.SaveChanges) 
       ctx.RemoveSentData(); 
      } 
      catch (FaultException<MyClass> soapError) 
      { 
       // SOAP exception received 
       // We internally remove sent data (i.e. data with 
       // property Sent as true) from database A through entity framework 
       // (entities removed then context.SaveChanges) 
       ctx.RemoveSentData(); 
      } 
      catch (Exception ex) 
      { 
       // Some logging here 
       return; 
      } 

      ts.Complete(); 
     } 
    } 
} 
catch (Exception ex) 
{ 
    // Some logging here (removed from code) 
    throw; 
} 

這是我的客戶端配置:

<system.serviceModel> 
    <bindings> 
     <wsHttpBinding> 
     <binding name="WsHttpBinding_IMyService" 
       closeTimeout="00:01:00" 
       openTimeout="00:01:00" 
       receiveTimeout="00:10:00" 
       sendTimeout="00:01:00" 
       allowCookies="false" 
       bypassProxyOnLocal="false" 
       hostNameComparisonMode="StrongWildcard" 
       transactionFlow="true" 
       maxBufferPoolSize="2147483647" 
       maxReceivedMessageSize="2147483647" 
       messageEncoding="Text" 
       textEncoding="utf-8" 
       useDefaultWebProxy="true"> 

      <readerQuotas maxDepth="32" 
         maxStringContentLength="2147483647" 
         maxArrayLength="16384" 
         maxBytesPerRead="4096" 
         maxNameTableCharCount="16384" /> 
      <security mode="None"> 
      <transport clientCredentialType="None" proxyCredentialType="None" realm="" /> 
      <message clientCredentialType="UserName" algorithmSuite="Default" /> 
      </security> 
     </binding> 
     </wsHttpBinding> 
    </bindings> 

    <client> 
     <endpoint address="http://localhost:8080/MyService.svc" 
       binding="wsHttpBinding" 
       bindingConfiguration="WsHttpBinding_IMyService" 
       contract="XXX.IMyService" 
       name="WsHttpBinding_IMyService" /> 
    </client> 

    </system.serviceModel> 

所以我的問題是:

  • 是我的模式/設計分佈式事務的有效和強大的設計?
  • 如果我的客戶端事務被終止了,可能會導致我的數據庫B無限時間被鎖定?
  • 我應該改變什麼(配置和/或代碼和/或設計)使其按預期工作(即,如果我的客戶端進程死亡/崩潰,我希望事務中止以便數據庫B未鎖定)?

謝謝。


編輯

我認爲我過於簡單化了我的實際使用情況的說明。看起來我實際上是在多個SQL Server實例之間進行簡單的數據複製。這不是很好解釋(我的壞),那不是我想要達到的。

我不是簡單地複製數據。我在機器M1上讀取A,然後寫入機器M2上的B,但寫入的內容不是我讀過的內容,而是一些計算的值,它來自讀取的內容。 我敢肯定SQL Server複製服務可以處理的業務計算爲我,但我不能因爲某些原因走這條路:

  • 我不能使用SQL Server的複製,因爲我其實不負責的服務器端(寫入數據庫B)。我甚至不確定在另一端是否會有SQL Server(可能是MySQL,PostgreSQL或任何其他的Java後臺)
  • 我不能使用SQL Server服務代理或任何面向消息的中間件(它會適合國際海事組織)出於同樣的原因(潛在的異構數據庫和環境)
  • 我無法更改webservices的東西,因爲現在定義和修復了接口。

我被WCF卡住了,我甚至無法更改綁定配置以使用MSMQ或任何因爲互操作性的要求。 MSMQ確實很棒(我已經在項目的另一部分使用了它),但它只是Windows。 SOAP 1.2是標準協議,並且SOAP 1.2事務也是標準協議(WS-Atomic實現)。

也許WCF事務不是一個好主意,實際上。

如果我已經正確理解它是如何工作的(請糾正我,如果我錯了),它只是允許「繼續」在服務器端的事務範圍,這將需要在服務器端配置事務協調器,這可能會破壞我的互操作性需求(同樣,服務器端的數據庫可能與事務協調器不能很好地集成)。

+0

相關問題[如何同步WCF服務中的數據庫訪問](http://stackoverflow.com/questions/10166571/how-to-synchronize-database-access-in-a-wcf-service)。 – 2012-04-19 15:48:10

+0

@JoshuaDrake嗨,感謝您的鏈接,但我實際上沒有併發問題。我確實有多個線程,但每個線程都在查詢其「自己的」數據庫。我甚至不會同時在同一個數據庫上運行多個事務:一切都同步運行,因此我一次只執行一個事務(客戶端讀取/更新數據庫A,然後將數據發送到服務器,然後服務器寫入DB B,最後客戶端從A中刪除)。 – ken2k 2012-04-19 15:56:44

回答

2

也許這不是你正在尋找的答案,但你只是重新發明輪子。您需要一種可靠的方式來複制從A到B的更改。已經有一個內置解決方案,即Transactional Replication。只需將其配置爲不復制刪除,並且您完全具有所要求的語義。

對於事務複製不適合賬單的事情(您描述的複雜拓撲結構,許多發佈者和許多訂閱者都可能是這種情況),我仍然不會使用WCF。 SQL Server已經具有可靠的消息傳送功能,這種消息傳送速度非常快,並且明確地設計爲避免分佈式事務和兩階段提交:Service Broker

即使您堅持WCF,使用協調事務並不是正確的方法。您應該使用MSMQ排隊通道,並以面向消息的方式進行設計,而不是RPC調用方式(並且通過http的WCF/HTTP調用WCF綁定只不過是一種榮耀的RPC)。我強烈建議你閱讀The Game of Distributed Systems Programming 。您現在處於第1級,您需要轉換到第2級.WCF HTTP不會將您帶到那裏。帶有MSMQ綁定的WCF可能,但你很快就會意識到編排排隊頻道沒有什麼像編程一個RPC頻道,你必須重寫幾乎所有的東西,從頭反正。如果您願意放棄Kool Aid,SSB可以將您帶到更可靠更快的車輛中。

+0

+1完全同意這種方法是天真的。 – 2012-04-20 07:09:36

+0

感謝您的回答。實際上,由於互操作性的原因,我不能使用事務複製,服務代理和MSMQ。請看我更新的問題(對不起,問題的第一個版本可能不夠準確)。我真的堅持WCF + SOAP。 – ken2k 2012-04-20 08:47:26