sql >> Databasteknik >  >> RDS >> Sqlserver

Vet du när du ska försöka igen eller misslyckas när du anropar SQL Server från C#?

En enda SqlException (kan) omsluter flera SQL Server-fel. Du kan iterera genom dem med Errors fast egendom. Varje fel är SqlError :

foreach (SqlError error in exception.Errors)

Varje SqlError har en Class egendom du kan använda för att grovt bestämma om du kan försöka igen eller inte (och i fall du försöker igen om du måste återskapa anslutningen också). Från MSDN:

  • Class <10 är för fel i information som du skickat, då kan du (förmodligen) inte försöka igen om du först inte korrigerar inmatningar.
  • Class från 11 till 16 är "genererade av användare" så kan du förmodligen igen inte göra någonting om användaren först inte korrigerar sina inmatningar. Observera att klass 16 innehåller många tillfälliga fel och klass 13 är för dödlägen (tack vare EvZ) så du kan utesluta dessa klasser om du hanterar dem en efter en.
  • Class från 17 till 24 är generella hårdvaru-/mjukvarufel och du kan försöka igen. När Class är 20 eller högre måste du återskapa anslutningen för. 22 och 23 kan vara allvarliga hårdvaru-/mjukvarufel, 24 indikerar ett mediafel (något användaren bör varnas men du kan försöka igen om det bara var ett "tillfälligt" fel).

Du kan hitta en mer detaljerad beskrivning av varje klass här.

I allmänhet om du hanterar fel med deras klass behöver du inte veta exakt varje fel (med error.Number egenskap eller exception.Number som bara är en genväg för första SqlError i den listan). Detta har nackdelen att du kan försöka igen när det inte är användbart (eller felet inte kan återställas). Jag skulle föreslå en tvåstegsmetod :

  • Sök efter kända felkoder (lista felkoder med SELECT * FROM master.sys.messages ) för att se vad du vill hantera (att veta hur). Den vyn innehåller meddelanden på alla språk som stöds så du kan behöva filtrera dem efter msglangid kolumn (till exempel 1033 för engelska).
  • För allt annat lita på felklass, försöker igen när Class är 13 eller högre än 16 (och återansluter om 20 eller högre).
  • Fel med svårighetsgrad högre än 21 (22, 23 och 24) är allvarliga fel och lite väntan kommer inte att lösa de problemen (databasen i sig kan också vara skadad).

Ett ord om högre klasser. Hur man hanterar dessa fel är inte enkelt och det beror på många faktorer (inklusive riskhantering för din ansökan). Som ett enkelt första steg skulle jag inte försöka igen för 22, 23 och 24 när jag försöker en skrivoperation:om databas, filsystem eller media är allvarligt skadade kan skrivning av ny data försämra dataintegriteten ännu mer (SQL Server är extremt försiktig med att kompromissa inte DB för en fråga även under kritiska omständigheter). En skadad server, det beror på din DB-nätverksarkitektur, kan till och med hot-swapas (automatiskt, efter en viss tid, eller när en specificerad trigger aktiveras). Rådgör alltid och arbeta nära din DBA.

Strategin för att försöka igen beror på felet du hanterar:lediga resurser, vänta på att en väntande operation ska slutföras, vidta en alternativ åtgärd, etc. I allmänhet bör du bara försöka igen om alla fel är "försök på nytt":

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

Slå in allt i try /finally för att avyttra anslutningen på rätt sätt. Med denna enkla falska naiva 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...
}

Några ganska knepiga sätt att hitta en lista över icke-kritiska fel här.

Vanligtvis bäddar jag in all denna (boilerplate) kod i en metod (där jag kan gömma alla smutsiga saker gjort för att skapa/avyttra/återskapa anslutning) med denna signatur:

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

För att användas så här:

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

Observera att skeleton (försök igen vid fel) även kan användas när du inte arbetar med SQL Server (det kan faktiskt användas för många andra operationer som I/O och nätverksrelaterade saker så jag föreslår att du skriver en allmän funktion och att återanvända det i stor utsträckning).



  1. Failover &Failback för PostgreSQL på Microsoft Azure

  2. Använda PostgreSQL-replikeringsplatser

  3. MySQL ger alla privilegier till databasen utom en tabell

  4. Hur får man veckodagens namn från ett datum?