2015-09-28 17 views
9

Wir machen erhebliche Verwendung von Entity Framework in einer Datenbank erstes Modell mit Entity Framework 6 und SqlSever 2012.Entity Framework Deadlocks und Concurrency

Wir haben eine Reihe von ziemlich langen laufenden Prozesse (10 von Sekunden), die jeweils eine erstellen Objekt des gleichen Typs mit unterschiedlichen Daten Diese Objekte schreiben und löschen Daten in der Datenbank unter Verwendung des Entity-Frameworks. So weit, ist es gut. Um die Leistung der Anwendung zu verbessern, wir suchen läuft diese Operationen parallel und als solche das Task Konstrukt verwenden, dies zu achive wie folgt:

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 

wir laufen 10 davon und auf sie warten alle auf Ergänzen mit 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 

dieses gut ~ 90% der Zeit arbeitet die anderen 10% ist damit der Datenbankserver mit einem inneren deadlocking Ausnahme

alle Prozessoren verwenden die gleichen Tabellen Deadlocks aber teilen Sie absolut keine Daten zwischen Prozessen (und hängen nicht von denselben FK-Zeilen ab) und erstellen Sie alle ihre eigenen Entityframework-Kontexte ohne gemeinsame Interaktion zwischen ihnen.

Überprüfung Profilierungsverhalten der Sql Server Instanz sehen wir eine große Anzahl von sehr kurzlebigen Sperren Akquisitionen und Releases zwischen jeder erfolgreichen Abfrage. Was zu einer eventuellen Deadlockkette bis:

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

die Schlösser selbst von KEY Typ sind und die deadlocking Abfragen sind alle für den gleichen Tisch, aber mit verschiedenen Schlüsseln der Form:

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

Wir relativ neu zum Entity-Framework und sind nicht in der Lage, die Ursache von scheinbar überspannten Sperren zu identifizieren (ich kann über sql profiler nicht feststellen, welche Zeilen gerade gesperrt sind).

EDIT: deadlock.xdl

EDIT2: Aufruf saveChanges nach jedem Entfernen Anweisung, den Stillstand noch entfernt nicht ganz verstehen, warum es

+0

Haben Sie eine xdl-Datei verfügbar? Ist dies der Fall, überprüfen Sie die Transaktionsisolationsstufe für jeden der beteiligten Prozesse. Ich würde Dollars auf Donuts wetten, dass mindestens einer von ihnen auf "serialisierbar" eingestellt ist. –

+0

isolationlevel = "lesen Sie committed (2)" für alle – user2732663

+0

Sieht so aus, als hätte ich diese Wette verloren. :) Können Sie die XDL-Datei irgendwo zur Analyse bringen? –

Antwort

8

Sie erscheinen Deadlocks wurde das Opfer von Sperre Eskalations sein

Um die Leistung zu verbessern, wird Sql Server (und alle modernen DB-Engines) viele Low-Level-Feinkorn-Locks in einige High-Level-Grobkorn-Locks umwandeln. In Ihrem Fall geht es von Sperren auf Zeilenebene zu einer vollständigen Tabellensperre, nachdem der Schwellenwert überschritten wurde. Sie können dies auf verschiedene Arten beheben:

  1. Eine Lösung besteht darin, SaveChanges() aufzurufen, was Sie bereits getan haben. Dadurch wird die Sperre früher aufgehoben, wodurch eine Eskalation der Sperre von verhindert wird, da die Anzahl der unteren Sperren geringer ist als die Schwelle .
  2. Sie könnten auch die Isolationsstufe ändern, um nicht committed zu lesen, die würde die Anzahl der Sperren senken, indem Sie schmutzige Lesevorgänge zulassen, die verhindern würde, dass Sperren Eskalation auftreten.
  3. Schließlich sollten Sie in der Lage sein, einen Befehl ALTER TABLE mit SET (LOCK_ESCALATION = {AUTO | TABLE | DISABLE}) zu senden. Tabelle Level-Sperren sind jedoch weiterhin möglich, auch wenn deaktiviert. MSDN zeigt auf das Beispiel des Scannens einer Tabelle ohne gruppierten Index unter einer Serialisierung-Isolationsstufe. Siehe hier: https://msdn.microsoft.com/en-us/library/ms190273(v=sql.110).aspx

In Ihrem Fall die Lösung, die Sie von save Änderungen haben, was in der Transaktion festgeschrieben werden und die Sperre aufgehoben wird, ist die bevorzugte Option.

0

Ein Deadlock tritt auf, wenn 2 (oder mehr) Prozesse die gleichen Ressourcen benötigen, aber jeder Prozess seine Ressourcen in einer anderen Reihenfolge erwirbt. Der (ziemlich komplizierte) Weg, dies zu vermeiden, besteht darin, die Ressourcen in eine bestimmte Reihenfolge zu bringen. Wenn Sie beispielsweise eine Reihe von Datensätzen (des gleichen Typs) aktualisieren, aktualisieren Sie die Datensätze in der Reihenfolge ihres Primärschlüssels.

Ein einfacherer Weg ist es, einen Stamm Ihrer Transaktion zu nominieren - einen Eltern-Datensatz und diesen immer zuerst zu aktualisieren. Wenn Sie also Ihre Tabellen in logische Bits aufteilen - DDD ruft diese Aggregate auf und hat einen einzigen Codebereich, der für die Aktualisierung jedes Aggregats zuständig ist, dann kann der Code für ein bestimmtes Aggregat immer zuerst den Stamm des Aggregats aktualisieren, bevor er fummelt (jetzt in beliebiger Reihenfolge) Sie mögen) mit den Kindtabellen in der Gesamtheit.

Auf diese Weise versuchen zwei oder mehr Operationen auf dem gleichen Aggregat (sagen ein Kunden-Aggregat mit CustomerId 123) beide Kunden aggregieren root zu aktualisieren. Einer wird gewinnen und der andere wird blockiert (aber nicht festgefahren), bis der Gewinner seine Änderungen vorgenommen hat. Dieser Ansatz hilft Ihnen auch sicherzustellen, dass Invarianten in dem Aggregat gepflegt werden, und mehr eine Versionsnummer/Zeitmarke auf dem Stamm können Sie nach (sonst versteckten) Updates suchen.

EF kann Ihnen hier nicht helfen - Sie müssen möglicherweise den Stamm aktualisieren und Änderungen speichern, um sicherzustellen, dass EF nicht die Reihenfolge Ihrer Updates entscheidet und die Wurzel zuletzt setzt - was den Zweck vereiteln würde.