sql >> Base de Datos >  >> RDS >> Sqlserver

¿Sabe cuándo reintentar o fallar al llamar a SQL Server desde C#?

Una sola SqlException (puede) envuelve múltiples errores de SQL Server. Puede iterar a través de ellos con Errors propiedad. Cada error es SqlError :

foreach (SqlError error in exception.Errors)

Cada SqlError tiene una Class propiedad que puede usar para determinar aproximadamente si puede volver a intentarlo o no (y en caso de que vuelva a intentarlo, si también tiene que volver a crear la conexión). Desde MSDN:

  • Class <10 es para errores en la información que pasó, entonces (probablemente) no puede volver a intentarlo si primero no corrige las entradas.
  • Class del 11 al 16 son "generados por el usuario", entonces probablemente nuevamente no pueda hacer nada si el usuario primero no corrige sus entradas. Tenga en cuenta que la clase 16 incluye muchos servicios temporales y la clase 13 es para interbloqueos (gracias a EvZ), por lo que puede excluir estas clases si las maneja una por una.
  • Class del 17 al 24 son errores genéricos de hardware/software y puede volver a intentarlo. Cuando Class es 20 o más tienes que recrear la conexión también. 22 y 23 pueden ser errores graves de hardware/software, 24 indica un error de medios (algo que se debe advertir al usuario, pero puede volver a intentarlo en caso de que solo sea un error "temporal").

Puede encontrar una descripción más detallada de cada clase aquí.

En general, si maneja errores con su clase, no necesitará saber exactamente cada error (usando error.Number propiedad o exception.Number que es solo un atajo para el primer SqlError en esa lista). Esto tiene el inconveniente de que puede volver a intentarlo cuando no es útil (o el error no se puede recuperar). Sugeriría un enfoque de dos pasos :

  • Verifique los códigos de error conocidos (enumere los códigos de error con SELECT * FROM master.sys.messages ) para ver lo que quieres manejar (saber cómo). Esa vista contiene mensajes en todos los idiomas admitidos, por lo que es posible que deba filtrarlos por msglangid columna (por ejemplo, 1033 para inglés).
  • Para todo lo demás, confíe en la clase de error, vuelva a intentarlo cuando Class tiene 13 años o más de 16 (y se vuelve a conectar si tiene 20 años o más).
  • Los errores con una gravedad superior a 21 (22, 23 y 24) son errores graves y un poco de espera no solucionará esos problemas (la propia base de datos también puede dañarse).

Una palabra sobre las clases superiores. Cómo manejar estos errores no es simple y depende de muchos factores (incluyendo la gestión de riesgos para su aplicación). Como un primer paso simple, no volvería a intentarlo para 22, 23 y 24 cuando intente una operación de escritura:si la base de datos, el sistema de archivos o los medios están gravemente dañados, escribir datos nuevos puede deteriorar aún más la integridad de los datos (SQL Server tiene mucho cuidado de no comprometa DB para una consulta incluso en circunstancias críticas). Un servidor dañado, depende de la arquitectura de red de su base de datos, incluso podría ser intercambiado en caliente (automáticamente, después de un período de tiempo específico, o cuando se activa un activador específico). Siempre consulte y trabaje cerca de su DBA.

La estrategia para reintentar depende del error que esté manejando:liberar recursos, esperar a que se complete una operación pendiente, realizar una acción alternativa, etc. En general, debe reintentar solo si todos los errores son "reintentables":

bool rebuildConnection = true; // First try connection must be open

for (int i=0; i < MaximumNumberOfRetries; ++i) {
    try {
        // (Re)Create connection to SQL Server
        if (rebuildConnection) {
            if (connection != null)
                connection.Dispose();

            // Create connection and open it...
        }

        // Perform your task

        // No exceptions, task has been completed
        break;
    }
    catch (SqlException e) {
        if (e.Errors.Cast<SqlError>().All(x => CanRetry(x))) {
            // What to do? Handle that here, also checking Number property.
            // For Class < 20 you may simply Thread.Sleep(DelayOnError);

            rebuildConnection = e.Errors
                .Cast<SqlError>()
                .Any(x => x.Class >= 20);

            continue; 
        }

        throw;
    }
}

Envuelva todo en try /finally para desechar correctamente la conexión. Con este simple-falso-ingenuo CanRetry() función:

private static readonly int[] RetriableClasses = { 13, 16, 17, 18, 19, 20, 21, 22, 24 };

private static bool CanRetry(SqlError error) {
    // Use this switch if you want to handle only well-known errors,
    // remove it if you want to always retry. A "blacklist" approach may
    // also work: return false when you're sure you can't recover from one
    // error and rely on Class for anything else.
    switch (error.Number) {
        // Handle well-known error codes, 
    }

    // Handle unknown errors with severity 21 or less. 22 or more
    // indicates a serious error that need to be manually fixed.
    // 24 indicates media errors. They're serious errors (that should
    // be also notified) but we may retry...
    return RetriableClasses.Contains(error.Class); // LINQ...
}

Algunas formas bastante complicadas de encontrar una lista de errores no críticos aquí.

Por lo general, incrusto todo este código (repetitivo) en un método (donde puedo ocultar todas las cosas sucias hecho para crear/eliminar/recrear la conexión) con esta firma:

public static void Try(
    Func<SqlConnection> connectionFactory,
    Action<SqlCommand> performer);

Para ser usado así:

Try(
    () => new SqlConnection(connectionString),
    cmd => {
             cmd.CommandText = "SELECT * FROM master.sys.messages";
             using (var reader = cmd.ExecuteReader()) {
                 // Do stuff
         }
    });

Tenga en cuenta que el esqueleto (reintentar en caso de error) también se puede usar cuando no está trabajando con SQL Server (en realidad, se puede usar para muchas otras operaciones como E/S y cosas relacionadas con la red, por lo que sugiero escribir una función general y reutilizarlo extensivamente).