2015-09-28 18 views
9

Robimy znaczne wykorzystanie Entity Framework w bazie danych pierwszego modelu z Entity Framework 6 i SqlSever 2012.Entity Framework Blokady i współbieżność

Mamy szereg dość długich procesów biegania (10-tych sekund), którą każdy Utwórz obiekt tego samego typu z różnymi danymi, które te obiekty w trakcie tworzenia zapisują i usuwają dane w bazie danych za pomocą struktury encji. Jak na razie dobrze. W celu poprawy wydajności aplikacji Szukamy uruchamia te operacje równolegle i jako taki jest używany Task konstrukt do achive to w następujący sposób:

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 

prowadzimy 10 z nich i czekać na nich wszystkich do wypełnić drukowanymi 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 

to działa dobrze ~ 90% czasu pozostałe 10% to zakleszczenia serwer bazy danych z wewnętrzną wyjątkiem dodatkowego blokowania

wszystkie procesory wykorzystują te same tabele ale nie udostępniaj absolutnie żadnych danych między procesami (i nie zależą one od tych samych wierszy FK) i twórz wszystkie własne struktury ramek encji bez interakcji współdzielonej między nimi.

Przeglądanie zachowania profilowania instancji Sql Server Widzimy dużą liczbę bardzo krótkich przejęć i uwolnień blokady między każdym pomyślnym zapytaniem. Prowadzące do ewentualnego łańcucha impasu:

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

The zamki sami są typu KEY i zapytania deadlocking są w tej samej tabeli, ale z różnych kluczy postaci:

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

Mamy stosunkowo nowy do struktury encji i nie są w stanie zidentyfikować głównej przyczyny tego, co wydaje się zbyt dużym blokadom (nie jestem w stanie zidentyfikować za pomocą narzędzia profilowania sql wierszy, które są blokowane).

EDIT: deadlock.xdl

EDIT2: Wywoływanie saveChanges po każdym oświadczenie remove usuwa impasu nadal nie bardzo rozumiem dlaczego został deadlocking

+0

Czy masz dostępny plik xdl? Jeśli tak, sprawdź poziom izolacji transakcji dla każdego z zaangażowanych procesów. Stawiałbym pieniądze na pączki, że przynajmniej jeden z nich jest ustawiony na "serializowalny". –

+0

isolationlevel = "przeczytaj zatwierdzone (2)" dla wszystkich – user2732663

+0

Wygląda na to, że przegrałem ten zakład. :) Czy umieścisz plik XDL gdzieś do analizy? –

Odpowiedz

8

Jesteś wydają się być ofiarą Blokada Escalation

Aby zwiększyć wydajność, serwer Sql (i wszystkie nowoczesne silniki DB) zamienia wiele blokad drobnego ziarna o niskim poziomie w kilka blokad wysokiego ziarna o wysokim poziomie. W twoim przypadku, po przekroczeniu progu, przechodzi z blokady poziomu wiersza do pełnej blokady tabeli. Możesz rozwiązać ten problem na kilka sposobów:

  1. Jednym z rozwiązań jest wywołanie SaveChanges(), który już zrobiłeś. Spowoduje to zwolnienie blokady wcześniej, zapobiegając eskalacji blokady od występującej, ponieważ niższa liczba blokad = mniejsze prawdopodobieństwo trafienia do progu eskalacji .
  2. Można również zmienić poziom izolacji, aby odczytywać niezaakceptowane, co spowoduje zmniejszenie liczby blokad przez zezwalanie na brudne odczyty, co spowoduje, że nie wystąpi eskalacja blokady.
  3. Na koniec powinieneś móc wysłać polecenie ALTER TABLE, używając SET (LOCK_ESCALATION = {AUTO | TABLE | DISABLE}). Jednak blokady tabeli są nadal możliwe, nawet gdy są wyłączone. MSDN wskazuje na przykład skanowania tabeli bez indeksu klastrowego pod serializowalnego poziomu izolacji. Zobacz tutaj: https://msdn.microsoft.com/en-us/library/ms190273(v=sql.110).aspx

w Twoim przypadku, rozwiązanie trzeba dzwonić zapisać zmiany, w wyniku transakcji popełniane a zamek jest zwolniony to opcja korzystna.

0

Zakleszczenie występuje, gdy 2 (lub więcej) procesów wymaga tych samych zasobów, ale każdy proces pozyskuje zasoby w innej kolejności. (Bardzo skomplikowanym) sposobem na uniknięcie tego jest uzyskanie zasobów w określonej kolejności. Tak więc, na przykład, aktualizując kilka rekordów (tego samego typu) aktualizuj rekordy w kolejności ich klucza podstawowego.

Prostszym sposobem jest wskazanie katalogu głównego Twojej transakcji - jakiegoś rekordu nadrzędnego i zawsze najpierw go aktualizuj. Jeśli segregujesz swoje tabele na logiczne bity - DDD wywołuje te Agregaty i ma jeden obszar kodu odpowiedzialny za aktualizację każdego agregatu, wtedy kod dla konkretnego agregatu zawsze może zaktualizować główny składnik agregatu najpierw przed manipulowaniem (teraz w dowolnej kolejności lubisz) z tabelami podrzędnymi w postaci zagregowanej.

W ten sposób dwie lub więcej operacji na tym samym agregacie (na przykład moduł Customer Aggregate z CustomerId 123) będą próbować zaktualizować główny katalog klienta. Jedna wygra, a druga zostanie zablokowana (ale nie zablokowana), dopóki zwycięzca nie dokona zmian. Takie podejście pomoże również zapewnić utrzymanie niezmienników w agregacie, a ponadto numer wersji/znacznik czasu w katalogu głównym pozwoli na sprawdzenie (w przeciwnym razie ukrytych) aktualizacji.

EF może ci w tym nie pomóc - być może będziesz musiał najpierw zaktualizować katalog główny i zapisać zmiany, aby EF nie zdecydował o kolejności aktualizacji i nie ustawił korzenia jako ostatniego - co mogłoby zniweczyć cel.