Obsługa błędów i wzorce powtórek z go-mssqldb

Aplikacje Production Go wymagają uporządkowanego zarządzania błędami, aby rozróżnić przejściowe awarie, które można powtórzyć, od trwałych błędów wymagających interwencji człowieka. Ten artykuł obejmuje klasyfikację błędów, wzorce ponownych prób oraz strategie odporności dla kierowcy go-mssqldb .

Struktura błędów SQL Server

Gdy SQL Server zwraca błąd, go-mssqldb sterownik owija go strukturąmssql.Error. Użyj asercji typu, aby uzyskać dostęp do strukturyzowanych pól błędu:

import (
    "database/sql"
    "errors"
    "fmt"

    mssql "github.com/microsoft/go-mssqldb"
)

func handleError(err error) {
    var mssqlErr mssql.Error
    if errors.As(err, &mssqlErr) {
        fmt.Printf("Number:  %d\n", mssqlErr.Number)
        fmt.Printf("State:   %d\n", mssqlErr.State)
        fmt.Printf("Class:   %d\n", mssqlErr.Class)
        fmt.Printf("Message: %s\n", mssqlErr.Message)
        fmt.Printf("Server:  %s\n", mssqlErr.ServerName)
        fmt.Printf("Proc:    %s\n", mssqlErr.ProcName)
        fmt.Printf("Line:    %d\n", mssqlErr.LineNo)
    }
}

Pola błędu

Pole Typ Opis
Number int32 Numer błędu SQL Server. Mapuje na sys.messages.
State uint8 Stan błędu. Dostarcza dodatkowego kontekstu dla tego samego numeru błędu.
Class uint8 Poziom nasilenia (0-25). Poziomy ważności 11-16 mogą zostać skorygowane przez użytkownika. Poziom nasilenia 17+ wskazuje na problemy z zasobami lub systemem.
Message string Czytelny dla człowieka tekst błędu z serwera.
ServerName string Nazwa instancji SQL Server, która wywołała błąd.
ProcName string Nazwa procedury lub funkcji, w której wystąpił błąd. Puste dla zapytań ad hoc.
LineNo int32 Numer wiersza w partii Transact-SQL (T-SQL) lub procedurze składowanej.

Poziomy ważności

Zakres ważności Meaning Action
0-10 Komunikaty informacyjne Brak błędu. Loguj, jeśli się przyda.
11-16 Błędy poprawiane przez użytkownika Napraw zapytanie, parametry lub uprawnienia.
17-19 Błędy zasobów Ponów próbę. Serwer może być obciążony lub bez zasobów.
20-25 Błędy śmiertelne Połączenie jest przerwane. Połącz się ponownie i spróbuj ponownie.

Klasyfikuj błędy jako przejściowe lub trwałe

Błędy przejściowe to stany tymczasowe, które same się rozwiązują, takie jak zakłócenia sieciowe, ograniczanie połączeń czy krótkie spory o zasoby. Trwałe błędy wymagają zmian kodu lub konfiguracji.

Typowe liczby błędów przejściowych

Użyj następującego współdzielonego katalogu jako kanonicznej listy przejściowych błędów nawiązywania połączenia i transportu ścieżki żądań:

Następujące błędy są przejściowe, gdy występują podczas nawiązywania połączenia lub podczas wysyłania żądania do serwera. Ponów próbę na krótkim, ograniczonym wycofywaniu. Błędy, które utrzymują się po kilku ponownych próbach, zwykle wskazują problem z konfiguracją (niewłaściwy serwer, brakujące uprawnienia, wyczerpany limit przydziału), które ponawianie próby nie zostaną rozwiązane.

Error Message Troubleshooting
64 A connection was successfully established with the server, but then an error occurred during the login process. (provider: TCP Provider, error: 0 - The specified network name is no longer available.) Połączenie TCP zostaje przerwane w trakcie negocjacji. To nie jest błąd poświadczenia. Jeśli problem będzie się powtarzał, sprawdź, czy po stronie klienta nie występuje niestabilność sieci lub czy urządzenie pośredniczące nie zrywa połączeń na wpół ustanowionych.
233 The client was unable to establish a connection because of an error during connection initialization process before login. Błąd transportu przed logowaniem lub błąd protokołu TLS. Serwer często zwraca go, gdy nie może zaakceptować połączenia (wyczerpanie zasobów, osiągnięto maksymalną liczbę połączeń lub nieobsługiwanego klienta). To nie jest błąd poświadczenia. Sprawdź kondycję serwera, a następnie sprawdź limit czasu logowania klienta, ustawienia protokołu TLS i zgodność wersji protokołu TLS klienta/serwera.
4060 Cannot open database "%.*ls" requested by the login. The login failed. Logowanie uwierzytelnia się, ale nie może otworzyć żądanej bazy danych. Przejściowe przyczyny obejmują sytuacje, w których baza danych jest w trakcie zmiany stanu (przełączenie awaryjne, przywracanie, skalowanie) lub została automatycznie wstrzymana. Trwałe przyczyny (baza danych nie istnieje, konto logowania nie ma dostępu) nie zostaną usunięte przez ponowienie próby; sprawdź nazwę bazy danych, mapowanie konta logowania oraz stan bazy danych.
4221 Login to read-secondary failed due to long wait on 'HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING'. Replika nie jest dostępna do logowania, ponieważ brakuje wersji wierszy dla transakcji, które były w locie podczas recyklingu repliki. Wycofaj lub zatwierdź aktywne transakcje na serwerze podstawowym, aby rozwiązać problem. Ogranicz to ryzyko, unikając długotrwałych transakcji zapisu na serwerze głównym.
10053 A transport-level error has occurred when sending the request to the server. (provider: TCP Provider, error: 0 - An established connection was aborted by the software in your host machine.) Strona lokalna zrywa połączenie. Sprawdź stan sieci po stronie klienta oraz lokalną zaporę lub klienta VPN.
10054 A transport-level error has occurred when sending the request to the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) Po stronie zdalnej jest wysyłane resetowanie protokołu TCP. Typowe przyczyny: proces równorzędny uległ awarii, zapora wymusiła reset połączenia lub brama Azure SQL zamknęła bezczynne połączenie. W przypadku resetowania połączenia z powodu bezczynności włącz mechanizm TCP keepalive po stronie klienta lub skróć limit czasu bezczynności w puli połączeń.
10928 Resource ID: %d. The %s limit for the database is %d and has been reached. See 'http://go.microsoft.com/fwlink/?LinkId=267637' for assistance. Baza danych przekracza limit nadzoru nad zasobami Azure SQL. Identyfikator zasobu 1 oznacza limit procesów roboczych; identyfikator zasobu 2 oznacza limit sesji. Zidentyfikuj typ limitu na podstawie komunikatu, a następnie zmniejsz współbieżność, zwiększ zasoby bazy danych lub skróć długotrwałe operacje blokujące zasób.
10929 Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d, and the current usage for the database is %d. However, the server is currently too busy to support requests greater than %d for this database. Baza danych przekroczyła swoje minimalne gwarantowane zasoby, a serwer bazowy ogranicza jej wydajność. Ponowienie próby zwykle kończy się powodzeniem, gdy obciążenie sąsiada spadnie. Trwałe wystąpienia wskazują, że potrzebujesz wyższej warstwy usług lub mniej hałaśliwego środowiska.
40020, 40143, 40166, 40540 Zgłoszone w Error code %d miejscu błędu 40197 podczas pracy w trybie failover. Podkody osadzone w komunikacie failover 40197, które w niektórych ścieżkach wykonania są zwracane jako kod błędu najwyższego poziomu. Traktuj je tak samo jak 40197.
40197 The service has encountered an error processing your request. Please try again. Error code %d. Uaktualnienie oprogramowania, awaria sprzętu lub inne zdarzenie trybu failover w Azure SQL. Ponowne połączenie kieruje do sprawnej repliki. Wbudowany kod błędu identyfikuje typ przełączenia awaryjnego. Jeśli błąd będzie się powtarzać, przechwyć identyfikator śledzenia sesji i skontaktuj się z pomocą techniczną.
40501 The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. Ograniczanie silnika Azure SQL Zalecane minimalne opóźnienie wynosi 10 sekund. Długotrwałe ograniczanie wydajności wskazuje, że obciążenie przekroczyło przydzielone zasoby bazy danych; przejdź na wyższą warstwę usługi lub zmniejsz współbieżność.
40613 Database '%.*ls' on server '%.*ls' is not currently available. Please retry the connection later. If the problem persists, contact customer support, and provide them with the session tracing ID of '%.*ls'. Baza danych jest niedostępna, zwykle w połowie trybu failover lub krótko podczas operacji skalowania. Ponów próbę po odczekaniu; jeśli problem będzie się utrzymywał dłużej niż kilka minut, zapisz identyfikator śledzenia sesji i otwórz zgłoszenie do działu pomocy technicznej.
42108 Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. Dedykowana pula SQL (Synapse) jest wstrzymana. Ponowienie próby zakończy się powodzeniem dopiero po wznowieniu puli. Wznów pulę ręcznie lub zaplanuj uruchomienie obciążenia po wznowieniu puli.
42109 The SQL pool is warming up. Please try again. Dedykowana pula SQL jest wznawiana. Ponów próbę w przypadku wycofywania, dopóki pula nie będzie w trybie online; rozgrzewka zwykle trwa kilka minut.
49918 Cannot process request. Not enough resources to process request. The service is currently busy. Please retry the request later. Serwer nie może obecnie przydzielić wystarczającej ilości zasobów, aby spełnić żądanie. Ponów próbę z opóźnieniem. Jeśli błąd będzie się powtarzać, przeprowadź skalowanie w górę bazy danych lub elastycznej puli.
49919 Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". Limit współbieżności na poziomie subskrypcji dla operacji zarządzania. Zmniejszenie równoległych wywołań tworzenia/aktualizacji lub ich rozłożenie.
49920 Cannot process request. Too many operations in progress for subscription "%ld". Limit współbieżności na poziomie subskrypcji dla operacji w locie. Zmniejsz równoległość lub poczekaj, aż trwające operacje się zakończą.

Błędów na poziomie instrukcji nie ma na tej liście, ponieważ występują po nawiązaniu połączenia, a awaria pozostawia sesję w stanie używalnym. Najczęstsze błędy instrukcji, które można ponowić, to 1205 (ofiara impasu) i 1222 (przekroczenie limitu czasu żądania blokady). Ponów całą transakcję zamiast pojedynczej instrukcji, która nie powiodła się.

Tekst komunikatu o błędzie pochodzi z błędów połączenia przejściowego Azure SQL. Poszczególne sterowniki utrzymują własne wbudowane listy ponawiania prób; W tym wykazie opisano, które błędy kwalifikują się do ponawiania próby między SQL Server, Azure SQL Database, Azure SQL Managed Instance, bazą danych SQL w Microsoft Fabric i dedykowanymi pulami SQL w programie Azure Synapse Analytics.

Poniższa isTransient funkcja przedstawia jeden ze wzorców implementacji w Go na potrzeby klasyfikacji ponowień. Uznaj powyższy współdzielony katalog za miarodajne źródło i utrzymuj wyszukiwanie kodów w spójności z nim.

// isTransient returns true if the error is a transient SQL Server error
// that is likely to succeed on retry.
func isTransient(err error) bool {
    var mssqlErr mssql.Error
    if !errors.As(err, &mssqlErr) {
        // Network errors, context deadlines, and connection resets
        // are also transient.
        return isNetworkError(err)
    }

    if isTransientSQLNumber(mssqlErr.Number) {
        return true
    }

    // Severity 17-19 indicates resource issues that are typically transient.
    return mssqlErr.Class >= 17 && mssqlErr.Class <= 19
}

// Keep this lookup synchronized with the shared transient catalog above.
var transientSQLNumbers = map[int32]struct{}{
    64:    {}, // Transport/connection error.
    1205:  {}, // Deadlock victim.
    40197: {}, // Service error processing request.
    40501: {}, // Service is currently busy.
    40613: {}, // Database is currently unavailable.
    49918: {}, // Cannot process request: not enough resources.
    49919: {}, // Cannot process create/update request.
    49920: {}, // Cannot process request: too many operations.
}

func isTransientSQLNumber(number int32) bool {
    _, ok := transientSQLNumbers[number]
    return ok
}

Jeśli napotkasz błędy konfiguracji i kwot, napraw podstawową pojemność, bazę danych lub konfigurację sieci przed ponowną próbą. Oto kilka przykładów:

  • 40544 (limit wielkości bazy danych)
  • 4060 (nie można otworzyć bazy danych)
  • 40615 (reguła zapory)

Wykrywanie błędów sieciowych

Błędy na poziomie sieci nie generują mssql.Error wartości. Sprawdź typowe typy błędów sieci Go:

import (
    "context"
    "errors"
    "net"
    "io"
)

func isNetworkError(err error) bool {
    if err == nil {
        return false
    }

    // Context deadline exceeded or canceled
    if errors.Is(err, context.DeadlineExceeded) {
        return true
    }

    // Connection reset or broken pipe
    var netErr *net.OpError
    if errors.As(err, &netErr) {
        return true
    }

    // Unexpected EOF (server dropped the connection)
    if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {
        return true
    }

    return false
}

Zaimplementuj ponawianie z wykładniczym opóźnieniem

Powtarzaj błędy przejściowe z rosnącymi opóźnieniami między próbami. Takie podejście daje serwerowi czas na odzyskanie sprawności i zapobiega przeciążeniu go często ponawianymi próbami.

import (
    "context"
    "database/sql"
    "errors"
    "fmt"
    "log"
    "math"
    "math/rand"
    "time"

    mssql "github.com/microsoft/go-mssqldb"
)

// RetryConfig controls retry behavior.
type RetryConfig struct {
    MaxAttempts int           // Maximum number of attempts (including the first).
    BaseDelay   time.Duration // Initial delay before the first retry.
    MaxDelay    time.Duration // Upper bound on delay between retries.
}

// DefaultRetryConfig provides sensible defaults for SQL Server workloads.
var DefaultRetryConfig = RetryConfig{
    MaxAttempts: 5,
    BaseDelay:   100 * time.Millisecond,
    MaxDelay:    10 * time.Second,
}

// RetryFunc executes fn with retries for transient errors.
func RetryFunc(ctx context.Context, cfg RetryConfig, fn func(ctx context.Context) error) error {
    var lastErr error
    for attempt := 0; attempt < cfg.MaxAttempts; attempt++ {
        lastErr = fn(ctx)
        if lastErr == nil {
            return nil
        }

        if !isTransient(lastErr) {
            return lastErr // Permanent error, don't retry.
        }

        if attempt == cfg.MaxAttempts-1 {
            break // Last attempt, don't sleep.
        }

        delay := calculateDelay(attempt, cfg.BaseDelay, cfg.MaxDelay)

        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-time.After(delay):
        }
    }
    return lastErr
}

func calculateDelay(attempt int, baseDelay, maxDelay time.Duration) time.Duration {
    // Exponential backoff: base * 2^attempt
    delay := time.Duration(float64(baseDelay) * math.Pow(2, float64(attempt)))
    if delay > maxDelay {
        delay = maxDelay
    }
    // Add jitter: +/- 25% to avoid thundering herd
    jitter := time.Duration(rand.Int63n(int64(delay) / 2))
    return delay/2 + jitter
}

// isTransient classifies retryable SQL Server errors.
// For a fuller example, see "Classify errors as transient or permanent" earlier in this article.
func isTransient(err error) bool {
    var mssqlErr mssql.Error
    if !errors.As(err, &mssqlErr) {
        return errors.Is(err, context.DeadlineExceeded)
    }

    switch mssqlErr.Number {
    case 1205, 40197, 40501, 40613, 49918, 49919, 49920:
        return true
    }

    return mssqlErr.Class >= 17 && mssqlErr.Class <= 19
}

// getEmployeeCount wraps a query with automatic retry.
func getEmployeeCount(ctx context.Context, db *sql.DB) (int, error) {
    var count int
    err := RetryFunc(ctx, DefaultRetryConfig, func(ctx context.Context) error {
        // This query executes on every retry attempt until success or exhaustion.
        return db.QueryRowContext(ctx, "SELECT COUNT(*) FROM HumanResources.Employee").Scan(&count)
    })
    return count, err
}

// Example call site (assumes db is already initialized).
func example(ctx context.Context, db *sql.DB) {
    queryCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
    defer cancel()

    count, err := getEmployeeCount(queryCtx, db)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Employee count: %d\n", count)
}

Obsługa zakleszczeń

Zablokowania (błąd 1205) są najczęstszym błędem przejściowym w aplikacjach wieloużytkownikowych. SQL Server automatycznie kończy jedną z konkurencyjnych sesji i zwraca ofierze błąd 1205.

Wykryj impas

Sprawdź, czy błąd SQL Server jest zakleszczeniem (błąd 1205).

func isDeadlock(err error) bool {
    var mssqlErr mssql.Error
    if errors.As(err, &mssqlErr) {
        return mssqlErr.Number == 1205
    }
    return false
}

Próba ponownego wykonania transakcji po zablokowaniu

Gdy w transakcji dochodzi do impasu, serwer cofa całą transakcję. Musisz ponownie spróbować całej transakcji, a nie tylko oświadczenia o nieudanej transakcji:

func transferInventory(ctx context.Context, db *sql.DB, productID, fromLocationID, toLocationID int, qty int) error {
    return RetryFunc(ctx, DefaultRetryConfig, func(ctx context.Context) error {
        tx, err := db.BeginTx(ctx, &sql.TxOptions{
            Isolation: sql.LevelReadCommitted,
        })
        if err != nil {
            return err
        }
        defer tx.Rollback()

        _, err = tx.ExecContext(ctx,
            "UPDATE Production.ProductInventory SET Quantity = Quantity - @qty WHERE ProductID = @pid AND LocationID = @lid",
            sql.Named("qty", qty),
            sql.Named("pid", productID),
            sql.Named("lid", fromLocationID))
        if err != nil {
            return err
        }

        _, err = tx.ExecContext(ctx,
            "UPDATE Production.ProductInventory SET Quantity = Quantity + @qty WHERE ProductID = @pid AND LocationID = @lid",
            sql.Named("qty", qty),
            sql.Named("pid", productID),
            sql.Named("lid", toLocationID))
        if err != nil {
            return err
        }

        return tx.Commit()
    })
}

Wskazówka

Redukuj blokady, uzyskując dostęp do tabel w spójnej kolejności we wszystkich transakcjach i utrzymując je krótkie.

Ponowne próbowanie jest poprawną odpowiedzią w kodzie aplikacji, ale powtarzające się impaski na tym samym zapytaniu wskazują na problem projektowy. Użyj grafu zakleszczeń SQL Server (przechwyconego za pomocą Extended Events lub sesji System Health), aby zidentyfikować kolidujące instrukcje i typy blokad. Pełny przegląd analizy i zapobiegania impasom znajdziesz w przewodniku Deadlocks. Informacje na temat strategii obsługi zakleszczeń specyficznych dla transakcji można znaleźć w sekcji Obsługa zakleszczeń.

Obsługa wyczerpania basenu przyłączowego

Gdy wszystkie połączenia w puli są używane i zostanie osiągnięty limit MaxOpenConns, nowe wywołania są blokowane, aż połączenie stanie się dostępne lub upłynie termin kontekstu. Ta sytuacja objawia się powolnymi żądaniami lub błędami przekroczenia limitu czasu kontekstu, a nie bezpośrednimi błędami wyczerpania puli.

Wykrywanie ciśnienia w basenie

Monitoruj statystyki puli i generuj alert, gdy liczba oczekiwań wzrasta.

func monitorPool(ctx context.Context, db *sql.DB) {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()

    var lastWaitCount int64
    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            stats := db.Stats()
            newWaits := stats.WaitCount - lastWaitCount
            lastWaitCount = stats.WaitCount

            if newWaits > 0 {
                log.Printf("Pool pressure: open=%d inUse=%d idle=%d newWaits=%d waitDuration=%v",
                    stats.OpenConnections, stats.InUse, stats.Idle,
                    newWaits, stats.WaitDuration)
            }
        }
    }
}

Typowe przyczyny i rozwiązania

Objaw Przyczyna Rozwiązanie
WaitCount stale rośnie MaxOpenConns jest za niski Zwiększ MaxOpenConns, aby dopasować go do poziomu współbieżności.
InUse jest równe MaxOpenConns przez dłuższy czas Połączenia nie są zwracane do puli Zamknij *sql.Rows, zatwierdź lub wycofaj *sql.Tx, a następnie niezwłocznie zamknij *sql.Conn.
OpenConnections ciągle rośnie Połączenia wyciekają szybciej, niż MaxIdleConns może je odzyskać Ustaw ConnMaxLifetime i ConnMaxIdleTime, aby ograniczyć wiek połączenia.
Przekroczono limit czasu kontekstu podczas wykonywania zapytań Pula jest przesycona, a dzwoniący czekają zbyt długo Zwiększ rozmiar puli, skróć czas wykonania zapytań lub dodaj limity zapytań.

Obsługa konkretnych błędów SQL Server

Naruszenia ograniczeń

Naruszenia unikalnych kluczy i klucza obcego to trwałe błędy wskazujące na problem logiczny w aplikacji:

func isUniqueViolation(err error) bool {
    var mssqlErr mssql.Error
    if errors.As(err, &mssqlErr) {
        return mssqlErr.Number == 2627 || // Unique constraint violation
            mssqlErr.Number == 2601       // Unique index violation
    }
    return false
}

func isForeignKeyViolation(err error) bool {
    var mssqlErr mssql.Error
    if errors.As(err, &mssqlErr) {
        return mssqlErr.Number == 547 // FK constraint violation
    }
    return false
}

Wzorzec upsert z wykrywaniem konfliktów

Użyj instrukcji MERGE, aby atomowo wstawić lub zaktualizować wiersz:

func upsertDepartment(ctx context.Context, db *sql.DB, id int, name, groupName string) error {
    _, err := db.ExecContext(ctx, `
        MERGE INTO HumanResources.Department AS target
        USING (SELECT @id AS DepartmentID, @name AS Name, @grp AS GroupName) AS source
        ON target.DepartmentID = source.DepartmentID
        WHEN MATCHED THEN
            UPDATE SET Name = source.Name, GroupName = source.GroupName
        WHEN NOT MATCHED THEN
            INSERT (Name, GroupName) VALUES (source.Name, source.GroupName);`,
        sql.Named("id", id),
        sql.Named("name", name),
        sql.Named("grp", groupName))
    return err
}

Błędy uprawnień

Wykrywać typowe kody błędów odmowy dostępu, aby zwracać wywołującym jasny komunikat:

func isPermissionError(err error) bool {
    var mssqlErr mssql.Error
    if errors.As(err, &mssqlErr) {
        return mssqlErr.Number == 229 ||   // SELECT permission denied
            mssqlErr.Number == 230 ||       // Column permission denied
            mssqlErr.Number == 262 ||       // CREATE permission denied
            mssqlErr.Number == 300 ||       // VIEW permission denied
            mssqlErr.Number == 15247        // User doesn't have permission
    }
    return false
}

Zajmij się SQL. ErrNoRows

sql.ErrNoRowsto nie błąd SQL Server. Metoda QueryRowContext.Scan zwraca go, gdy zapytanie nie zwraca wierszy. Obsłuż to jawnie, aby odróżnić „nie znaleziono” od faktycznych błędów:

func getEmployee(ctx context.Context, db *sql.DB, id int) (*Employee, error) {
    var emp Employee
    err := db.QueryRowContext(ctx,
        "SELECT TOP (1) BusinessEntityID, FirstName + ' ' + LastName AS Name, CountryRegionName AS Location FROM Sales.vSalesPerson WHERE BusinessEntityID = @p1",
        sql.Named("p1", id)).Scan(&emp.Id, &emp.Name, &emp.Location)

    if errors.Is(err, sql.ErrNoRows) {
        return nil, nil // Not found, not an error.
    }
    if err != nil {
        return nil, fmt.Errorf("query employee %d: %w", id, err)
    }
    return &emp, nil
}

Opakuj błędy kontekstem

Dodaj kontekst do błędów, aby dzwoniący mogli zrozumieć, gdzie doszło do awarii:

func getEmployeesByLocation(ctx context.Context, db *sql.DB, location string) ([]Employee, error) {
    rows, err := db.QueryContext(ctx,
        "SELECT BusinessEntityID, FirstName + ' ' + LastName AS Name, CountryRegionName AS Location FROM Sales.vSalesPerson WHERE CountryRegionName = @p1",
        sql.Named("p1", location))
    if err != nil {
        return nil, fmt.Errorf("query employees by location %q: %w", location, err)
    }
    defer rows.Close()

    var employees []Employee
    for rows.Next() {
        var emp Employee
        if err := rows.Scan(&emp.Id, &emp.Name, &emp.Location); err != nil {
            return nil, fmt.Errorf("scan employee row: %w", err)
        }
        employees = append(employees, emp)
    }
    if err := rows.Err(); err != nil {
        return nil, fmt.Errorf("iterate employee rows: %w", err)
    }
    return employees, nil
}

Użycie %w zachowuje łańcuch błędów, dzięki czemu wywołujący mogą nadal używać errors.As i errors.Is kontrolować podstawowy błąd.

Lista kontrolna obsługi błędów

Area Zalecenie
Aseracja typu Deklaruj var mssqlErr mssql.Error, a następnie użyj errors.As(err, &mssqlErr) do dostępu do pól błędu SQL Server.
Wykrywanie zjawisk przejściowych Klasyfikuj błędy według liczby i stopnia nasilenia, zanim zdecydujesz, czy spróbować ponownie.
Logika ponawiania prób Użyj wycofywania wykładniczego z roztrzaskiem. Ustaw maksymalną liczbę prób i ogólny timeout na podstawie kontekstu.
Deadlocks Spróbuj ponownie całą transakcję, nie pojedyncze wyciągi. Ogranicz zakleszczenia, uzyskując dostęp do tabel w sposób spójny.
Wyczerpanie basenu Monitoruj db.Stats() i ustawiaj limity czasu kontekstu we wszystkich wywołaniach do bazy danych.
ErrNoRows Jawnie obsługuj sql.ErrNoRows dla QueryRowContext. To nie jest błąd serwera.
Zawijanie błędów Użyj fmt.Errorf razem z %w, aby dodać kontekst przy zachowaniu łańcucha błędów.
Naruszenia ograniczeń Sprawdź numery błędów 2627, 2601 (unikalny) i 547 (klucz obcy), aby sprawnie radzić sobie z konfliktami.