2011-10-09 9 views
5

Descubrió que OleDBConnection no parece ser ThreadSafe. Parece que intenta abrir conexiones múltiples en su lugar.ExecuteNonQuery en paralelo dentro de una OleDbConnection/OleDbTransaction compartida

//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(); 

     } 

}); 

Si bloqueo la conexión para un ExecuteNonQuery los errores desaparecen, pero los tanques de rendimiento.

//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(); 
      } 

      } 

    }); 

Supongamos que

  • que no puedo cambiar la naturaleza de la ORM: el SQL no puede ser abultado

  • Las reglas de negocio requieren que la interacción se lleva a cabo en una sola transacción

Así:

  • ¿Existe alguna forma mejor o más eficaz para paralelizar las interacciones OleDb?

  • En caso negativo, ¿existe alguna alternativa al cliente OleDb que pueda aprovechar al máximo el paralelismo? (Tal vez el cliente MSSQL nativa?)

Respuesta

6

Las transacciones deben ser ACID, pero la "Durabilidad" debe aplicarse solo al final de la transacción. Por lo tanto, la IO física en el disco puede posponerse después de la ejecución aparente de la declaración SQL y realmente se realiza en segundo plano, mientras la transacción procesa otras declaraciones.

Como consecuencia, la emisión de sentencias SQL en serie no puede ser mucho más lento que los emite simultáneamente . Considere este escenario:

  • Ejecute la instrucción SQL [A] que escribe datos.El disco no se toca realmente, las escrituras simplemente se colocan en cola para más adelante, por lo que el flujo de ejecución regresa muy rápido al cliente (es decir, [A] no bloquea durante mucho tiempo).
  • Ejecute la instrucción SQL [B] que escribe datos. Las escrituras están en cola y [B] no se bloquea por mucho tiempo, al igual que antes. La E/S física de [A] ya puede estar ocurriendo en segundo plano en este punto.
  • Se lleva a cabo otro procesamiento en la transacción, mientras que DBMS realiza la E/S física en el disco en segundo plano.
  • La transacción está comprometida.
    • Si las escrituras en cola han finalizado, no hay necesidad de esperar.
    • Si las escrituras en cola no están terminadas, espere hasta que estén. Por cierto, algunas bases de datos pueden relajar los requisitos de "durabilidad" para evitar esta espera, pero no el servidor MS SQL (AFAIK).

Por supuesto, hay escenarios en los que esta "auto-paralelismo" de DBMS no funcionaría bien, por ejemplo, cuando hay una cláusula WHERE que por diferentes declaraciones toca diferentes particiones de discos diferentes - DBMS amaría para paralelizar estas cláusulas, pero no pueden si se las alimenta uno a uno.

En cualquier caso, no adivine dónde está su cuello de botella de rendimiento. ¡Mídelo en su lugar!


Por cierto, Marte no le ayudará en la paralelización de los estados de cuenta - de acuerdo a MSDN: "Nótese, sin embargo, que Marte se define en términos de intercalación, no en términos de la ejecución en paralelo."

+0

Mucha información excelente aquí. Para su punto de medición, me temo que lo que termino es de manzanas a naranjas: puedo comparar 1 conexión y 1 transacción ejecutándose en serie a 80,000 conexiones y transacciones en paralelo (con agrupación). Entre ellos, hay una diferencia de 15 segundos (aproximadamente un minuto y medio en total). Dependiendo de cuán eficientemente funcione la agrupación, esperaba un ahorro sustancial si pudiera mantener la misma conexión. – seraphym

+1

Adición: El escenario anterior con 1 conexión + bloqueo sale a unos 10 segundos más rápido que el serial, y unos 5 segundos más lento que el 'paralelo completo' – seraphym

1

descubierto que OLEDBConnection no parece ser THREADSAFE.

Sí, eso es de acuerdo con la documentation:

estáticos públicos (Shared en Visual Basic) de este tipo son seguros para subprocesos . No se garantiza que ningún miembro de la instancia sea seguro .

Así que simplemente cree la conexión dentro de la cadena y deje que el proveedor OLE DB subyacente maneje la agrupación de conexiones. Además, si tienes la posibilidad, deshazte de OleDbConnection y utiliza el controlador ADO.NET correspondiente para tu base de datos y, a menos que estés ejecutando alguna base de datos muy exótica, debería haber un controlador ADO.NET.

+0

Necesito compartir la transacción: las reglas de negocio dictan que todo retroceda en la falla. – seraphym

1

Dado que no es seguro para la rosca, cambie el Parallel.ForEach a uno normal foreach y hágalo en serie. Es mejor para que funcione más lento que para nada.

0

Para obtener la mayor ganancia de rendimiento, abra una nueva conexión dentro de su Parallel.ForEach. De esa manera tendrá conexiones paralelas verdaderas a la base de datos.

Asegúrese de tener habilitada la agrupación de conexiones y la propiedad de conexión mínima y máxima configurada adecuadamente.

Pruebe este enfoque y use la clase Cronómetro para cronometrar el rendimiento entre distintos enfoques y elija el que mejor funcione en su caso. Depende del tipo de consultas que ejecutará en la base de datos y el esquema.

Cuestiones relacionadas