2015-09-28 89 views
9

我們做數據庫中的第一款車型大量使用實體框架的實體框架6和SQLSEVER 2012實體框架死鎖和併發

我們擁有一批相當長時間運行的進程(秒10的),每個創建的這些對象在創建時使用實體框架在數據庫中寫入和刪除數據。到現在爲止還挺好。爲了提高我們正在尋找應用程序的性能在並行運行這些操作,因此使用Task結構來實現這一目標如下:

Private Async Function LongRunningProcessAsync(data As SomeData) As Task(Of LongRunningProcessResult) 
    Return Await Task.Factory.StartNew(Of LongRunningProcessResult)(Function() 
                 Return Processor.DoWork(data) 
                End Function)    
End Function 

我們運行的這10個,並等待他們全部完成使用Task.WaitAll

Class Processor 
    Public Function DoWork(data As SomeData) As LongRunningProcessResult 
     Using context as new dbContext() 
      ' lots of database calls 
      context.saveChanges() 
     end Using 

     ' call to sub which creates a new db context and does some stuff 
     doOtherWork() 

     ' final call to delete temporary database data 
     using yetAnotherContext as new dbContext() 
      Dim entity = yetAnotherContext.temporaryData.single(Function(t) t.id = me.Id) 
      yetAnotherContext.temporaryDataA.removeAll(entity.temporaryDataA) 
      yetAnotherContext.temporaryDataB.removeAll(entity.temporaryDataB) 
      yetAnotherContext.temporaryData.remove(entity) 

      ' dbUpdateExecption Thrown here 
      yetAnotherContext.SaveChanges() 
     end using 
    End Function 
End Class 

這個效果很好〜90%的時間,其他10%是鎖死與內部死鎖例外數據庫服務器

所有處理器使用相同的表,但在進程之間絕對不共享數據(並且不依賴於相同的FK行),並且在它們之間沒有共享交互的情況下創建所有自己的實體框架上下文。

查看Sql Server實例的性能分析行爲,我們看到每個成功查詢之間存在大量非常短暫的鎖定獲取和釋放。領導到一個最終的僵局鏈:

Lock:Deadlock Chain Deadlock Chain SPID = 80 (e413fffd02c3)   
Lock:Deadlock Chain Deadlock Chain SPID = 73 (e413fffd02c3)  
Lock:Deadlock Chain Deadlock Chain SPID = 60 (6cb508d3484c) 

的鎖本身KEY型和死鎖的查詢都是爲了同一個表,但與形式的不同的密鑰:

exec sp_executesql N'DELETE [dbo].[temporaryData] 
WHERE ([Id] = @0)',N'@0 int',@0=123 

我們比較新到實體框架,並且無法識別出現超大範圍鎖定的根本原因(我無法通過sql分析器確定確切的行被鎖定)。

編輯:deadlock.xdl

EDIT2:調用saveChanges每個刪除語句刪除死鎖後,仍然不明白爲什麼它被鎖死

+0

您是否有可用的xdl文件?如果是這樣,請檢查涉及的每個流程的事務隔離級別。我會打賭美元甜甜圈,至少其中一個被設置爲「可序列化」。 –

+0

isolationlevel =「閱讀承諾(2)」爲所有 – user2732663

+0

看起來我失去了那個賭注。 :)你可以把XDL文件放在某個地方進行分析嗎? –

回答

8

你似乎鎖升級的受害者

爲了提高性能,Sql Server(以及所有現代數據庫引擎)將許多低級別的細粒鎖轉換爲幾個高級粗粒鎖。在你的情況下,超過閾值後,它從行級鎖到全表鎖。您可以通過幾種不同的方法來解決這個問題:

  1. 一個解決方案是調用您已經完成的SaveChanges()。 這將更快地釋放鎖,防止發生鎖從 升級,因爲更低的鎖計數=不太可能達到升級 閾值。
  2. 您也可以將隔離級別更改爲未提交讀取,其中 通過允許髒讀取來降低鎖定數量,這也會阻止發生鎖定升級。
  3. 最後,您應該能夠使用SET (LOCK_ESCALATION = {AUTO | TABLE | DISABLE})發送ALTER TABLE命令。然而,即使在禁用時,表 級別鎖仍然是可能的。 MSDN指向 掃描不帶聚簇索引的表在 可序列化的隔離級別下的示例。在這裏看到: https://msdn.microsoft.com/en-us/library/ms190273(v=sql.110).aspx

在你的情況下,被釋放你調用save變化,導致交易被提交的解決方案,該鎖是較好的選擇。

0

當2個(或更多)進程需要相同的資源但每個進程以不同的順序獲取其資源時發生死鎖。避免這種情況的(相當複雜的)方法是按照一定的順序獲取資源。因此,例如,當更新一堆(相同類型的)記錄時,按主鍵的順序更新記錄。

更簡單的方法是提名您的交易的根 - 一些父記錄,並始終首先更新。因此,如果將表分隔成邏輯位--DDD調用這些聚合並且有一個負責更新每個聚合的代碼區域,那麼特定聚合的代碼總是可以在擺弄前先更新聚合的根(現在以任何順序你喜歡)與子表合在一起。

通過這種方式,對同一個聚合(例如CustomerId 123的Customer Aggregate)的兩個或多個操作都將嘗試更新Customer聚合根。一個人會贏,另一個會被阻止(但不會陷入僵局),直到獲勝者已經做出改變。這種方法還可以幫助您確保彙總中的不變式保持不變,並且根上的版本號/時間戳將允許您檢查(否則隱藏的)更新。

EF可能在這裏沒有幫助 - 您可能需要先更新根目錄並保存更改,以確保EF不會決定更新的順序並將根最後一位 - 這會導致失敗。