2015-09-28 21 views
9

Noi fare uso considerevole di Entity Framework in un primo modello di database con Entity Framework 6 e SqlSever 2012.Entity Framework deadlock e Concurrency

Abbiamo un certo numero di processi in esecuzione piuttosto lunghi (10 di secondi), che ciascun creare un oggetto dello stesso tipo con dati diversi questi oggetti nella loro creazione scrivono e cancellano i dati nel database usando il framework di entità. Fin qui tutto bene. Al fine di migliorare le prestazioni dell'applicazione stiamo cercando di corre queste operazioni in parallelo e in quanto tali stanno usando il Task costrutto per raggiungere questo obiettivo nel modo seguente:

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 

corriamo 10 di questi e attendere per tutti loro a completare con 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 

questo funziona bene ~ 90% del tempo il restante 10% è deadlock il server di database con un'eccezione deadlocking interna

tutti i processori utilizzano le stesse tabelle, ma non condividere assolutamente dati tra processi (e non dipendono dalle stesse righe FK) e creare tutti i propri contesti di entità esterna senza interazione condivisa tra loro.

esaminando il comportamento di creazione profili dell'istanza Sql Server, vediamo un gran numero di acquisizioni e rilasci di blocchi di durata molto breve tra ogni query completata. Che porta a una eventuale catena deadlock:

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

Le serrature stessi sono di tipo KEY e le query blocco critico sono tutti per la stessa tabella ma con diverse chiavi della forma:

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

Noi relativamente nuova al framework di entità e non riescono a identificare la causa principale di ciò che appare nei blocchi sovrascritti (non sono in grado di identificare tramite sql profiler le righe esatte bloccate).

EDIT: deadlock.xdl

EDIT2: Calling saveChanges dopo ogni istruzione remove toglie la situazione di stallo ancora non capisco il motivo per cui è stato deadlocking

+0

Hai un file xdl disponibile? In tal caso, controllare il livello di isolamento della transazione per ciascuno dei processi coinvolti. Scommetterei dollari a ciambelle che almeno uno di loro è impostato su "serializzabile". –

+0

isolationlevel = "read committed (2)" per tutti – user2732663

+0

Sembra che abbia perso la scommessa. :) Sei in grado di mettere il file XDL da qualche parte per l'analisi? –

risposta

8

Sembrate essere vittima di blocco Escalation

Per migliorare le prestazioni, il server Sql (e tutti i moderni motori DB) convertirà molti blocchi a grana fine di basso livello in alcuni blocchi di grana grossa di alto livello. Nel tuo caso, passa dai blocchi a livello di riga a un blocco completo della tabella dopo il superamento della soglia. Puoi risolvere questo in vari modi:

  1. Una soluzione è chiamare SaveChanges() che hai già fatto. Questo sbloccherà prima il blocco, impedendo l'escalation del blocco da , in quanto conteggio del blocco inferiore = meno probabilità di raggiungere la soglia di escalation .
  2. È anche possibile modificare il livello di isolamento per leggere senza impegno, che abbasserebbe il numero di blocchi consentendo letture sporche, il che farebbe sì che impedisca il verificarsi dell'escalation del blocco.
  3. Infine, dovresti essere in grado di inviare un comando ALTER TABLE utilizzando SET (LOCK_ESCALATION = {AUTO | TABLE | DISABLE}). Tuttavia i blocchi di livello sono ancora possibili anche quando sono disabilitati. MSDN punta a nell'esempio di scansione di una tabella senza indice cluster sotto un livello di isolamento serializzabile . Vedi qui: https://msdn.microsoft.com/en-us/library/ms190273(v=sql.110).aspx

Nel tuo caso, la soluzione si dispone di chiamare Salva modifiche, con conseguente l'operazione oggetto di commesso e la serratura di essere rilasciato è l'opzione preferibile.

0

Un deadlock si verifica quando 2 (o più) processi richiedono le stesse risorse ma ogni processo acquisisce le proprie risorse in un ordine diverso. Il modo (abbastanza complicato) per evitare questo è di ottenere le risorse in un ordine definito. Ad esempio, quando aggiorni un gruppo di record (dello stesso tipo) aggiorna i record nell'ordine della loro chiave primaria.

Un modo più semplice consiste nel nominare una radice della transazione, un record padre e aggiornarlo sempre per primo. Quindi, se si separano le tabelle in bit logici - DDD chiama questi Aggregati e dispone di una singola area di codice responsabile dell'aggiornamento di ciascun aggregato, quindi il codice per un particolare aggregato può sempre aggiornare la radice dell'aggregato prima di eseguire il giochino (ora in qualsiasi ordine ti piace) con le tabelle figlio nel complesso.

In questo modo due o più operazioni sullo stesso aggregato (ad esempio un aggregato cliente con CustomerId 123) tenteranno entrambi di aggiornare la radice aggregata del cliente. Uno vincerà e l'altro sarà bloccato (ma non bloccato) finché il vincitore non avrà commesso i suoi cambiamenti. Questo approccio ti aiuterà anche a mantenere invariati gli invarianti nell'aggregato e inoltre un numero di versione/timestamp sulla radice ti consentirà di verificare gli aggiornamenti (altrimenti nascosti).

EF potrebbe non essere d'aiuto: potrebbe essere necessario aggiornare la radice e salvare prima le modifiche per assicurarsi che EF non decida l'ordine degli aggiornamenti e metta la radice per ultimo, il che vanificherebbe lo scopo.