¿Cómo detectar SqlException causada por interbloqueo?

92

Desde una aplicación .NET 3.5 / C #, me gustaría capturar, SqlExceptionpero solo si es causado por interbloqueos en una instancia de SQL Server 2008.

El mensaje de error típico es Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Sin embargo, no parece ser un código de error documentado para esta excepción.

Filtrar la excepción contra la presencia de la palabra clave interbloqueo en su mensaje parece una forma muy fea de lograr este comportamiento. ¿Alguien sabe la forma correcta de hacer esto?

Joannes Vermorel
fuente
3
(Finalmente) encontré la documentación para el código de error: msdn.microsoft.com/en-us/library/aa337376.aspx . También puede encontrar esto a través de SQL Server:select * from master.dbo.sysmessages where error=1205
Martin McNulty

Respuestas:

154

El código de error específico de Microsft SQL Server para un interbloqueo es 1205, por lo que deberá manejar la SqlException y verificarlo. Entonces, por ejemplo, si para todos los demás tipos de SqlException desea que la burbuja muestre la excepción:

catch (SqlException ex)
{
    if (ex.Number == 1205)
    {
        // Deadlock 
    }
    else
        throw;
}

O, usando el filtrado de excepciones disponible en C # 6

catch (SqlException ex) when (ex.Number == 1205)
{
    // Deadlock 
}

Una cosa útil para encontrar el código de error SQL real para un mensaje dado es buscar en sys.messages en SQL Server.

p.ej

SELECT * FROM sys.messages WHERE text LIKE '%deadlock%' AND language_id=1033

Una forma alternativa de manejar los interbloqueos (de SQL Server 2005 y superior) es hacerlo dentro de un procedimiento almacenado utilizando el soporte TRY ... CATCH:

BEGIN TRY
    -- some sql statements
END TRY
BEGIN CATCH
    IF (ERROR_NUMBER() = 1205)
        -- is a deadlock
    ELSE
        -- is not a deadlock
END CATCH

Hay un ejemplo completo aquí en MSDN de cómo implementar la lógica de reintento de interbloqueo puramente dentro de SQL.

AdaTheDev
fuente
2
Tenga en cuenta los códigos de error son específicos del proveedor, por lo que 1205 es un callejón sin salida para SQL Server, pero pueden ser diferentes para Oracle, MySQL, etc.
brianmearns
3
Dependiendo de la capa de datos, el SqlExceptionpuede estar envuelto en otro. Por lo tanto, es posible que necesitemos capturar cualquier tipo de excepción y verificarlos, luego, si no son directamente una excepción de interbloqueo, verifique recursivamente su InnerException.
Frédéric
46

Debido a que supongo que posiblemente desee detectar interbloqueos, para poder volver a intentar la operación fallida, me gustaría advertirle sobre un pequeño problema. Espero que me disculpe por estar un poco fuera de tema aquí.

Un interbloqueo detectado por la base de datos revertirá efectivamente la transacción en la que estaba ejecutando (si la hubiera), mientras la conexión se mantiene abierta en .NET. Reintentar esa operación (en esa misma conexión), significa que se ejecutará en un contexto sin transacciones y esto podría conducir a la corrupción de datos.

Es importante estar consciente de esto. Es mejor considerar que la conexión completa está condenada al fracaso en caso de una falla causada por SQL. Reintentar la operación solo se puede hacer en el nivel donde se define la transacción (recreando esa transacción y su conexión).

Entonces, cuando vuelva a intentar una operación fallida, asegúrese de abrir una conexión completamente nueva y comenzar una nueva transacción.

Steven
fuente
4
¿Por qué necesita una conexión completamente nueva? He publicado una pregunta sobre esta respuesta aquí .
Sam
3

A continuación se muestra una forma de C # 6 de detectar interbloqueos.

try
{
    //todo: Execute SQL. 
    //IMPORTANT, if you used Connection.BeginTransaction(), this try..catch must surround that code. You must rollback the original transaction, then recreate it and re-run all the code.
}
catch (SqlException ex) when (ex.Number == 1205)
{
    //todo: Retry SQL
}

Asegúrese de que este try..catch rodee toda su transacción. De acuerdo con @Steven (vea su respuesta para más detalles), cuando el comando sql falla debido al punto muerto, hace que la transacción se revierta y, si no vuelve a crear la transacción, su reintento se ejecutará fuera del contexto de la transacción y puede resultar en inconsistencias de datos.

Brian
fuente