2011-10-09 8 views
5

Rilevato che OleDBConnection non sembra essere ThreadSafe. Sembra che tenti di aprire più connessioni.ExecuteNonQuery in parallelo all'interno di un oggetto OleDbConnection/OleDbTransaction condiviso

//doesn't work 
using (OleDbConnection oConn = TheDataAccessLayer.GetConnection()) 
using (OleDbTransaction oTran = oConn.BeginTransaction()) 
Parallel.ForEach(ORMObjects, (ORMObject, State) => 
{ 

     if (!State.ShouldExitCurrentIteration && !State.IsExceptional) 
     { 
       var Error = ORMObject.SomethingThatExecutesANonQuery(oConn,oTran) 

       if (Error.Number != 0) 
        State.Stop(); 

     } 

}); 

Se blocco la connessione per un ExecuteNonQuery gli errori vanno via, ma i serbatoi di prestazioni.

//works 
    using (OleDbConnection oConn = TheDataAccessLayer.GetConnection()) 
    using (OleDbTransaction oTran = oConn.BeginTransaction()) 
    Parallel.ForEach(ORMObjects, (ORMObject, State) => 
    { 

      if (!State.ShouldExitCurrentIteration && !State.IsExceptional) 
      { 
       lock(oConn) 
       { 
        var Error = ORMObject.SomethingThatExecutesANonQuery(oConn,oTran) 

       if (Error.Number != 0) 
         State.Stop(); 
      } 

      } 

    }); 

Si supponga che

  • Non posso cambiare la natura della ORM: lo SQL non può essere gonfiato

  • regole aziendali richiedono che l'interazione essere eseguita all'interno di una singola transazione

Quindi:

  • Esiste un modo migliore/più efficiente per parallelizzare le interazioni OleDb?

  • In caso contrario, esiste un'alternativa al client OleDb che può sfruttare appieno il parallelismo? (Forse il cliente MSSQL nativo?)

risposta

6

Le transazioni devono essere ACID, ma la "Durata" deve essere applicata solo alla fine della transazione. Pertanto, l'IO fisico sul disco può essere posticipato dopo l'apparente esecuzione dell'istruzione SQL e effettivamente eseguito in background, mentre la transazione sta elaborando altre istruzioni.

Di conseguenza, rilasciando dichiarazioni SQL serialmente non può essere molto più lento di loro rilascio contemporaneamente. Considerare questo scenario:

  • Eseguire l'istruzione SQL [A] che scrive i dati.Il disco non viene effettivamente toccato, le scritture vengono semplicemente accodate in un secondo momento, quindi il flusso di esecuzione ritorna molto rapidamente al client (ad esempio [A] non blocca a lungo).
  • eseguire l'istruzione SQL [B], che scrive i dati. Le scritture vengono messe in coda e [B] non si blocca a lungo, proprio come prima. L'I/O fisico di [A] potrebbe già accadere in background a questo punto.
  • Altri lavorazione ha luogo nella transazione, mentre DBMS esegue l'I/O fisico al disco in background.
  • La transazione è stata eseguita.
    • Se le scritture in coda sono terminate, non è necessario attendere.
    • Se scrive in coda non sono finiti, ormai, attendere che siano. A proposito, alcuni database possono attenuare i requisiti di "Durabilità" per evitare questa attesa, ma non MS SQL Server (AFAIK).

Naturalmente ci sono scenari in cui questo "auto-parallelismo" del DBMS non avrebbe funzionato bene, per esempio quando c'è una clausola WHERE che per le dichiarazioni diverse tocca diverse partizioni su dischi diversi - DBMS amerebbe per parallelizzare queste clausole, ma non possono farlo se vengono alimentate una ad una.

In ogni caso, non indovinare dove si trova il collo di bottiglia delle prestazioni. Misuralo invece!


BTW, MARS non vi aiuterà a parallelizzare le sue dichiarazioni - secondo MSDN: "Si noti, tuttavia, che Marte è definito in termini di interleaving, non in termini di esecuzione parallela."

+0

Un sacco di ottime informazioni qui. Per quanto riguarda il tuo punto di misura, ciò che finisco è, temo, mele alle arance: posso confrontare 1 connessione e 1 transazione eseguendo in serie fino a 80.000 connessioni e transazioni in parallelo (w/pooling). Tra questi, c'è una differenza di 15 secondi (circa un minuto e mezzo del tutto). A seconda di come funziona efficientemente il pooling, speravo in risparmi sostanziali se potessi mantenere la stessa connessione. – seraphym

+1

Addendum: Lo scenario di cui sopra con 1 collegamento + blocco esce per circa 10 secondi più veloce di serie e circa 5 secondi più lento di 'full parallela' – seraphym

1

Scoperto che OleDbConnection non sembra essere ThreadSafe.

Sì, che è in conformità con la documentation:

statici pubblici (in Visual Basic) di questo tipo sono thread-safe . Non è garantito che i membri di istanza siano protetti da thread .

Quindi è sufficiente creare la connessione all'interno del thread e lasciare che il provider OLE DB sottostante gestisca il pool di connessioni. Inoltre, se si ha la possibilità, eliminare definitivamente OleDbConnection e utilizzare il driver ADO.NET corrispondente per il proprio database e, a meno che non si stia utilizzando un database molto esotico, ci dovrebbe essere un driver ADO.NET.

+0

Ho bisogno di condividere la transazione: le regole aziendali impongono che tutto torni in caso di insuccesso. – seraphym

1

Poiché non è protetto da thread, modificare Parallel.ForEach in un normale foreach e eseguirli in modo seriale. È meglio che funzioni più lentamente del tutto.

0

Per ottenere il massimo guadagno in termini di prestazioni, aprire una nuova connessione all'interno di Parallel.ForEach. In questo modo avrai reali connessioni parallele al database.

Accertarsi che il pool di connessioni sia abilitato e che la proprietà di connessione minima e massima sia impostata correttamente.

Prova questo approccio e utilizza la classe di cronometro per misurare le prestazioni tra diversi approcci e scegliere quello che funziona meglio nel tuo caso. Dipende dal tipo di query che eseguirai contro il database e lo schema.

Problemi correlati