我遇到分佈式事務問題。SQL Server鎖問題(使用WCF的分佈式事務)
我正在使用SQL Server 2008 R2,Windows 7,.Net 4.0。
這是我真正想做的事:
- 我有一個緩衝數據庫(稱爲A)
- 我還有一個數據庫(稱爲B)
- 我想從數據庫中的數據發送到通過web服務WCF(HTTP上的SOAP,A和B不相同的機器上)
- 數據庫B如果數據被成功地發送到數據庫B,數據是從數據庫甲
請注意,這是一個非常簡單我現在遇到的問題。在我的真實應用程序中,我沒有在多個SQL Server實例之間複製數據(請參閱文章末尾的更新)。
一切都是事務的一部分,所以整個操作是原子的。
這裏就是我目前正在按順序做(在交易中的一切):
- 我從數據庫中的讀取塊(比如50行)
- 我更新在步驟1中讀取的行(我實際上設置了布爾值
Sent
列的值爲true
,所以在將來我知道這些行被髮送) - 我使用(客戶端)作爲事務一部分的WCF Web服務(SOAP 1.2,
wsHttpBinding
)流量(阻塞同步呼叫)。如果從服務器接收到的無例外的web服務請求發送(包含在web服務請求數據)在步驟1中 - web服務實現(服務器側)讀出的數據在數據庫中插入B數據
-
- ,與
Sent
值作爲true
數據從數據庫甲 - 除去如果特定
FaultException
從服務器接收到的,與所述Sent
值作爲真數據是從數據庫甲 - 除去用於其它
FaultExceptions
和其他異常(端點不foun d,或其他任何東西),與Sent
值true
數據不會從數據庫中刪除一個
- ,與
注:實際上有多個緩衝數據庫(A1,A2,A3,......,AN)和多個目標數據庫(B1,.... BN)。有N個線程來處理N個數據庫。
如果我運行我的服務器和我的客戶端,一切都工作得很好。數據從數據庫A到數據庫B以塊爲單位「原子」傳輸。 當我在事務中間(處理中止)時殘酷地停止我的客戶機時,大多數時候一切正常(即,數據不會從數據庫A中刪除,也不會添加到數據庫B中)。
但有時(這是我的實際問題),我的數據庫B被鎖定。解鎖它的唯一選擇是終止服務器進程。
當問題發生時,我可以在MSDTC中看到存在活動事務(請注意,截圖中有3個事務,因爲我目前正在處理3個緩衝區數據庫和3個目標數據庫,但有時存在即使有3個DB也只有1個交易)。
我看到嘗試運行SELECT
againt數據庫B.正如你可以看到下面的圖片時數據庫B被鎖定,我SELECT
請求由會話ID 67對應於INSERT
受阻數據庫B由服務器進程。
鎖保持永遠(沒有事務超時,即使經過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事務不是一個好主意,實際上。
如果我已經正確理解它是如何工作的(請糾正我,如果我錯了),它只是允許「繼續」在服務器端的事務範圍,這將需要在服務器端配置事務協調器,這可能會破壞我的互操作性需求(同樣,服務器端的數據庫可能與事務協調器不能很好地集成)。
相關問題[如何同步WCF服務中的數據庫訪問](http://stackoverflow.com/questions/10166571/how-to-synchronize-database-access-in-a-wcf-service)。 – 2012-04-19 15:48:10
@JoshuaDrake嗨,感謝您的鏈接,但我實際上沒有併發問題。我確實有多個線程,但每個線程都在查詢其「自己的」數據庫。我甚至不會同時在同一個數據庫上運行多個事務:一切都同步運行,因此我一次只執行一個事務(客戶端讀取/更新數據庫A,然後將數據發送到服務器,然後服務器寫入DB B,最後客戶端從A中刪除)。 – ken2k 2012-04-19 15:56:44