2010-11-10 24 views
5

Los registros de eventos para mi aplicación .NET muestran que ocasionalmente interbloqueos mientras se lee desde el servidor Sql. Esto generalmente es muy raro ya que ya hemos optimizado nuestras consultas para evitar interbloqueos, pero a veces aún ocurren. En el pasado, hemos tenido algunos bloqueos que ocurren al llamar a la función ExecuteReader en nuestra instancia SqlCommand. Para solucionar este código de reintento hemos añadido simplemente a ejecutar la consulta, así:Cómo recuperarse de la excepción de interbloqueo durante SqlDataReader.Read()

//try to run the query five times if a deadlock happends 
int DeadLockRetry = 5; 

while (DeadLockRetry > 0) 
{ 
    try 
    { 
     return dbCommand.ExecuteReader(); 
    } 
    catch (SqlException err) 
    { 
     //throw an exception if the error is not a deadlock or the deadlock still exists after 5 tries 
     if (err.Number != 1205 || --DeadLockRetry == 0) 
      throw; 
    } 
} 

Esto funcionó muy bien para el caso en que el punto muerto que sucedió durante la consulta inicial ejecutar, pero ahora estamos consiguiendo los puntos muertos mientras iterando a través de los resultados usando la función Read() en el SqlDataReader devuelto.

Una vez más, no estoy preocupado por la optimización de la consulta, sino simplemente tratando de recuperar en el raro caso de que se produzca un punto muerto. Estaba pensando en usar un proceso de reintento similar. Podría crear mi propia clase que hereda de SqlDataReader que simplemente anula la función Read con el código de reintento. De esta manera:

public class MyDataReader : SqlDataReader 
{ 
    public override bool Read() 
    { 
     int DeadLockRetry = 5; 
     while (DeadLockRetry > 0) 
     { 
      try 
      { 
       return base.Read(); 
      } 
      catch (SqlException ex) 
      { 
       if (ex.ErrorCode != 1205 || --DeadLockRetry == 0) 
        throw; 
      } 
     } 
     return false; 
    } 
} 

¿Es este el enfoque correcto? Quiero estar seguro de que los registros no se saltan en el lector. ¿Volverá a intentar Read después de un interbloqueo salte las filas? Además, ¿debo llamar al Thread.Sleep entre reintentos para dar tiempo a la base de datos para salir del estado de punto muerto, o es suficiente? Este caso no es fácil de reproducir, así que me gustaría estar seguro antes de modificar cualquiera de mis códigos.

EDIT:

Conforme a lo solicitado, algo más de información acerca de mi situación: En un caso, tengo un proceso que realiza una consulta que carga una lista de ID de registros que necesitan ser actualizados. Luego recorro esa lista de identificadores usando la función Read y ejecuto un proceso de actualización en ese registro, que eventualmente actualizará un valor para ese registro en la base de datos. (No, no hay forma de realizar la actualización en la consulta inicial, muchas otras cosas ocurren para cada registro que se devuelve). Este código ha estado funcionando bien por un tiempo, pero estamos ejecutando bastante código para cada registro, así que puedo imaginar que uno de esos procesos está creando un bloqueo en la tabla inicial que se está leyendo.

Después de pensarlo un poco, la sugerencia de Scottie de utilizar una estructura de datos para almacenar los resultados probablemente arreglaría esta situación. Podría almacenar los ID devueltos en un List<int> y recorrerlos. De esa forma, los bloqueos en las filas se pueden eliminar de inmediato.

Sin embargo, todavía estaría interesado en saber si hay una forma general de recuperarse de interbloqueos en una lectura.

+0

Podría dar más información sobre el uso por DB? Es extraño tener un punto muerto mientras se leen datos. Significa que su código ya tiene algunos bloqueos para otros datos y otra transacción ha bloqueado lo que está tratando de leer y está esperando los datos a los que ha accedido previamente. ¿Puedes llamar a ExecuteReader en otra transacción? – Timores

Respuesta

4

Toda su transacción se pierde en un punto muerto. Debe reiniciar desde cero, desde manera por encima del nivel donde lee un lector de datos. Usted dice que ha leído algunos registros y luego los actualiza en un bucle.Tiene que reiniciar de nuevo con la lectura de los registros:

function DoWork() { 
    using (TransactionScope scope = new TransactionScope(...)) { 
    cmd = new SqlCommand("select ..."); 
    using (DataReader rdr = cmd.ExecuteReader()) { 
     while(rdr.Read()) { 
      ... process each record 
     } 
    } 
    scope.Complete(); 
    } 
} 

que tienen para volver a intentar la llamada todaDoWork:

retries = 0; 
success = false; 
do { 
try { 
    DoWork(); 
    success = true; 
} 
catch (SqlException e) { 
    if (can retry e) { 
    ++retries; 
    } 
    else { 
    throw; 
    } 
} 
} while (!success); 
+0

Esto. No puede simplemente intentar volver a leer, necesita volver a ejecutar el comando. Pero definitivamente debe limitar el número de reintentos a 3-5 max, el SQL Server tarda algo de tiempo para manejar los deadlocks. –

+0

Ok gracias, esto es lo que estaba buscando. Tenemos una capa de datos abstraídos que devuelve IDataReaders para todas nuestras consultas de lectura, y luego nuestro código lógico usa los lectores. Entonces, en mi aplicación, la codificación de mis lecturas como sugirió no es fácil. Esperaba una solución general, pero parece que esto no es posible. Consideraré usar esta sugerencia para los casos específicos que están ocasionando bloqueos ocasionales. Gracias a todos por sus sugerencias! – InvisibleBacon

0

¿Podría considerar utilizar DataSets o DataTables en lugar de DataReaders?

Mi temor aquí, aunque no estoy seguro, es que la lectura arrojando el error descartará ese registro por completo. Es posible que desee probar esto en un entorno controlado donde puede forzar un error y ver si vuelve a leer el registro o si simplemente lo descarta.

+0

Estoy bastante seguro de que el uso de una DataTable o conjunto tendría las mismas vulnerabilidades. ¿.NET no usaría Read internamente para llenar el DataTable? Definitivamente me gustaría poder probar esto en un ambiente controlado. Sin embargo, no estoy seguro de qué causa los bloqueos, así que esto sería difícil. – InvisibleBacon

+0

No tiene que realizar pruebas de estancamiento específicamente. Si se encuentra en un entorno controlado, podría bajar el servidor SQL por un segundo y atrapar ESE error. Eso al menos le indicaría si el registro se vuelve a leer o si se descarta. – Scottie

+0

Sí, los DataSets usan los mismos mecanismos de lectura, pero me pregunto si MS ya tiene el código de interbloqueo en su lugar cuando lo lee. – Scottie

Cuestiones relacionadas