Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Wissen Sie, wann Sie es erneut versuchen oder fehlschlagen müssen, wenn Sie SQL Server von C# aufrufen?

Eine einzelne SqlException (kann) umschließt mehrere SQL Server-Fehler. Sie können sie mit Errors durchlaufen Eigentum. Jeder Fehler ist SqlError :

foreach (SqlError error in exception.Errors)

Jeder SqlError hat eine Class -Eigenschaft können Sie verwenden, um ungefähr zu bestimmen, ob Sie es erneut versuchen können oder nicht (und falls Sie es erneut versuchen, müssen Sie auch die Verbindung neu erstellen). Von MSDN:

  • Class <10 steht für Fehler in Informationen, die Sie übergeben haben, dann können Sie es (wahrscheinlich) nicht erneut versuchen, wenn Sie zuerst die Eingaben nicht korrigieren.
  • Class von 11 bis 16 sind "vom Benutzer generiert", dann können Sie wahrscheinlich wieder nichts tun, wenn der Benutzer seine Eingaben nicht zuerst korrigiert. Bitte beachten Sie, dass Klasse 16 viele vorläufige enthält Fehler und Klasse 13 ist für Deadlocks (dank EvZ), also können Sie diese Klassen ausschließen, wenn Sie sie einzeln behandeln.
  • Class von 17 bis 24 sind allgemeine Hardware-/Softwarefehler und Sie können es erneut versuchen. Wenn Class 20 oder höher ist, müssen Sie die Verbindung neu herstellen zu. 22 und 23 können schwerwiegende Hardware-/Softwarefehler sein, 24 weist auf einen Medienfehler hin (etwas, das der Benutzer warnen sollte, aber Sie können es erneut versuchen, falls es nur ein "vorübergehender" Fehler war).

Eine detailliertere Beschreibung der einzelnen Klassen finden Sie hier.

Wenn Sie Fehler mit ihrer Klasse behandeln, müssen Sie im Allgemeinen nicht jeden Fehler genau kennen (mithilfe von error.Number). Eigenschaft oder exception.Number was nur eine Abkürzung für den ersten SqlError ist in dieser Liste). Dies hat den Nachteil, dass Sie es möglicherweise erneut versuchen, wenn es nicht sinnvoll ist (oder ein Fehler nicht behoben werden kann). Ich würde einen Ansatz in zwei Schritten vorschlagen :

  • Nach bekannten Fehlercodes suchen (Fehlercodes auflisten mit SELECT * FROM master.sys.messages ), um zu sehen, was Sie handhaben möchten (wissen wie). Diese Ansicht enthält Nachrichten in allen unterstützten Sprachen, daher müssen Sie sie möglicherweise nach msglangid filtern Spalte (zum Beispiel 1033 für Englisch).
  • Verlassen Sie sich für alles andere auf die Fehlerklasse und versuchen Sie es erneut, wenn Class 13 oder höher als 16 ist (und Neuverbindung, wenn 20 oder höher).
  • Fehler mit einem Schweregrad über 21 (22, 23 und 24) sind schwerwiegende Fehler und ein wenig Warten wird diese Probleme nicht beheben (die Datenbank selbst kann auch beschädigt werden).

Ein Wort zu höheren Klassen. Der Umgang mit diesen Fehlern ist nicht einfach und hängt von vielen Faktoren ab (einschließlich Risikomanagement für Ihre Bewerbung). Als einfachen ersten Schritt würde ich es nicht für 22, 23 und 24 versuchen, wenn ich einen Schreibvorgang versuche:Wenn die Datenbank, das Dateisystem oder die Medien ernsthaft beschädigt sind, kann das Schreiben neuer Daten die Datenintegrität noch mehr verschlechtern (SQL Server ist äußerst vorsichtig). kompromittieren Sie die DB auch unter kritischen Umständen nicht für eine Abfrage). Ein beschädigter Server, das hängt von Ihrer DB-Netzwerkarchitektur ab, kann sogar im laufenden Betrieb ausgetauscht werden (automatisch, nach einer bestimmten Zeit oder wenn ein bestimmter Trigger ausgelöst wird). Wenden Sie sich immer an Ihren DBA und arbeiten Sie eng mit ihm zusammen.

Die Strategie für einen erneuten Versuch hängt von dem behandelten Fehler ab:Ressourcen freigeben, auf den Abschluss eines ausstehenden Vorgangs warten, eine alternative Aktion durchführen usw. Im Allgemeinen sollten Sie es nur wiederholen, wenn alle Fehler sind "retry-able":

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;
    }
}

Wickeln Sie alles in try ein /finally Verbindung ordnungsgemäß zu entsorgen. Mit diesem simplen, gefälschten, naiven CanRetry() Funktion:

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...
}

Einige ziemlich knifflige Wege, um hier eine Liste nicht kritischer Fehler zu finden.

Normalerweise bette ich all diesen (Boilerplate-)Code in eine Methode ein (wo ich verstecken kann all die schmutzigen Dinge getan, um eine Verbindung zu erstellen/entfernen/wieder herzustellen) mit dieser Signatur:

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

So zu verwenden:

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

Bitte beachten Sie, dass Skeleton (Retry on Error) auch verwendet werden kann, wenn Sie nicht mit SQL Server arbeiten (eigentlich kann es für viele andere Operationen wie E/A und netzwerkbezogene Dinge verwendet werden, daher würde ich vorschlagen, eine allgemeine Funktion zu schreiben und ausgiebig wiederzuverwenden).