Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Saiba quando tentar novamente ou falhar ao chamar o SQL Server de C#?


Uma única SqlException (pode) encapsula vários erros do SQL Server. Você pode iterar por eles com Errors propriedade. Cada erro é SqlError :
foreach (SqlError error in exception.Errors)

Cada SqlError tem uma Class propriedade que você pode usar para determinar aproximadamente se você pode tentar novamente ou não (e no caso de você tentar novamente se tiver que recriar a conexão também). Do MSDN:
  • Class <10 é para erros nas informações que você passou, então (provavelmente) você não pode tentar novamente se primeiro você não corrigir as entradas.
  • Class de 11 a 16 são "gerados pelo usuário", então provavelmente você não pode fazer nada se o usuário primeiro não corrigir suas entradas. Observe que a classe 16 inclui muitos temporários erros e a classe 13 é para deadlocks (graças a EvZ), então você pode excluir essas classes se lidar com elas uma a uma.
  • Class de 17 a 24 são erros genéricos de hardware/software e você pode tentar novamente. Quando Class for 20 ou mais, você precisa recriar a conexão também. 22 e 23 podem ser erros graves de hardware/software, 24 indica um erro de mídia (algo que o usuário deve ser avisado, mas você pode tentar novamente caso tenha sido apenas um erro "temporário").

Você pode encontrar uma descrição mais detalhada de cada classe aqui.

Em geral, se você lidar com erros com sua classe, você não precisará saber exatamente cada erro (usando error.Number propriedade ou exception.Number que é apenas um atalho para o primeiro SqlError nessa lista). Isso tem a desvantagem de que você pode tentar novamente quando não for útil (ou o erro não puder ser recuperado). Sugiro uma abordagem em duas etapas :
  • Verifique se há códigos de erro conhecidos (listar códigos de erro com SELECT * FROM master.sys.messages ) para ver o que você deseja manipular (saber como). Essa visualização contém mensagens em todos os idiomas suportados, portanto, talvez seja necessário filtrá-las por msglangid coluna (por exemplo, 1033 para inglês).
  • Para todo o resto, confie na classe de erro, tentando novamente quando Class é 13 ou superior a 16 (e reconectando se 20 ou superior).
  • Erros com gravidade superior a 21 (22, 23 e 24) são erros graves e pouca espera não resolverá esses problemas (o próprio banco de dados também pode ser danificado).

Uma palavra sobre as classes mais altas. Como lidar com esses erros não é simples e depende de muitos fatores (incluindo gerenciamento de riscos para sua aplicação). Como um primeiro passo simples, eu não tentaria novamente para 22, 23 e 24 ao tentar uma operação de gravação:se o banco de dados, o sistema de arquivos ou a mídia estiverem seriamente danificados, a gravação de novos dados pode deteriorar ainda mais a integridade dos dados (o SQL Server é extremamente cuidadoso para não comprometa o banco de dados para uma consulta, mesmo em circunstâncias críticas). Um servidor danificado, depende de sua arquitetura de rede de banco de dados, pode até ser trocado a quente (automaticamente, após um período de tempo especificado ou quando um gatilho especificado é acionado). Sempre consulte e trabalhe próximo ao seu DBA.

A estratégia para tentar novamente depende do erro que você está processando:liberar recursos, aguardar a conclusão de uma operação pendente, executar uma ação alternativa etc. Em geral, você deve tentar novamente somente se todos erros são "repetíveis":
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;
    }
}

Envolva tudo em try /finally para descartar corretamente a conexão. Com este simples-falso-ingênuo CanRetry() função:
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...
}

Algumas maneiras bastante complicadas de encontrar uma lista de erros não críticos aqui.

Normalmente eu incorporo todo esse código (boilerplate) em um método (onde posso ocultar todas as coisas sujas feito para criar/descartar/recriar conexão) com esta assinatura:
public static void Try(
    Func<SqlConnection> connectionFactory,
    Action<SqlCommand> performer);

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

Observe que o esqueleto (repetir no erro) também pode ser usado quando você não estiver trabalhando com o SQL Server (na verdade, ele pode ser usado para muitas outras operações, como E/S e coisas relacionadas à rede, então sugiro escrever uma função geral e reutilizá-lo extensivamente).