Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Production Go-applikationer behöver strukturerad felhantering för att skilja mellan tillfälliga fel som du kan försöka om och permanenta fel som kräver mänsklig ingripande. Den här artikeln behandlar felklassificering, återförsöksmönster och motståndsstrategier för föraren go-mssqldb .
Felstruktur i SQL Server
När SQL Server ger ett felmeddelande omsluter drivrutinen go-mssqldb det i en mssql.Error struktur. Använd en typassertion för att komma åt de strukturerade felfälten:
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)
}
}
Felfält
| Fält | Type | Description |
|---|---|---|
Number |
int32 |
SQL Server-felnummer. Mappar till sys.messages. |
State |
uint8 |
Feltillstånd. Ger ytterligare kontext för samma felnummer. |
Class |
uint8 |
Svårighetsgrad (0-25). Allvarlighetsgrad 11-16 kan korrigeras av användaren. Svårighetsgrad 17+ indikerar resurs- eller systemproblem. |
Message |
string |
Mänskligt läsbar feltext från servern. |
ServerName |
string |
Namnet på SQL Server-instansen som gav felet. |
ProcName |
string |
Lagrad procedur eller funktionsnamn där felet uppstod. Tomma för ad hoc-frågor. |
LineNo |
int32 |
Radnummer i Transact-SQL (T-SQL)-batchen eller lagrad procedur. |
Allvarlighetsgrad
| Allvarlighetsgrad | Meaning | Action |
|---|---|---|
| 0-10 | Informationsmeddelanden | Inget fel. Logga om det är användbart. |
| 11-16 | Användarkorrigerbara fel | Fixa frågan, parametrarna eller behörigheterna. |
| 17-19 | Resursfel | Försök igen. Servern kan vara under belastning eller utan resurser. |
| 20-25 | Dödliga misstag | Anslutningen är bruten. Återanslut och försök igen. |
Klassificera fel som övergående eller permanenta
Tillfälliga fel är tillfälliga tillstånd som löser sig själva, såsom nätverksstörningar, anslutningsbegränsning eller kortvarig resurskonkurrens. Permanenta fel kräver kod- eller konfigurationsändringar.
Vanliga tillfälliga felnummer
Använd följande delade katalog som den auktoritativa listan över övergående fel vid anslutningsetablering och transportfel längs begärans väg:
Följande fel är tillfälliga när de inträffar under anslutningsetableringen eller när en begäran skickas till servern. Försök igen på en kort, begränsad backoff. Fel som kvarstår efter några återförsök indikerar vanligtvis ett konfigurationsproblem (fel server, saknade behörigheter, uttömd kvot) som inte korrigeras igen.
| 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.) |
TCP-anslutningen bryts mitt under handskakningen. Inte ett autentiseringsfel. Om det kvarstår, kontrollera om det finns instabilitet i nätverket på klientsidan eller en mellanliggande enhet som bryter halvetablerade anslutningar. |
233 |
The client was unable to establish a connection because of an error during connection initialization process before login. |
Transportfel före inloggning eller TLS-fel. Servern returnerar den ofta när den inte kan acceptera anslutningen (resursöverbelastning, maximalt antal anslutningar har nåtts eller en klient som inte stöds). Inte ett autentiseringsfel. Kontrollera serverns hälsotillstånd och kontrollera sedan tidsgränsen för klientinloggning, TLS-inställningar och kompatibiliteten för klient-/server-TLS-versionen. |
4060 |
Cannot open database "%.*ls" requested by the login. The login failed. |
Inloggningen autentiserar men kan inte öppna den begärda databasen. Tillfälliga orsaker kan vara att databasen är i en övergångsfas (redundansväxling, återställning, skalning) eller har pausats automatiskt. Beständiga orsaker (databasen finns inte, inloggningen saknar åtkomst) kommer inte att åtgärdas genom ett nytt försök. kontrollera databasnamnet, inloggningsmappningen och databastillståndet. |
4221 |
Login to read-secondary failed due to long wait on 'HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING'. |
Repliken är inte tillgänglig för inloggning eftersom radversioner saknas för transaktioner som kördes när repliken återvanns. Återställ eller genomför de aktiva transaktionerna på primärservern för att lösa problemet. Minska risken genom att undvika långa skrivtransaktioner på primärnoden. |
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.) |
Den lokala sidan avbryter anslutningen. Kontrollera nätverkshälsa på klientsidan och eventuell lokal brandvägg eller VPN-klient. |
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.) |
Fjärrsidan skickar en TCP-återställning. Vanliga orsaker: peer-processen kraschade, en brandvägg injicerade en återställning eller Azure SQL gateway stängde en inaktiv anslutning. Vid återkommande återställningar efter inaktivitet aktiverar du TCP-keepalive på klienten eller förkortar anslutningspoolens tidsgräns för inaktivitet. |
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. |
Databasen överskrider en Azure SQL resursstyrningsgräns. Resurs-ID 1 anger arbetsgränsen. Resurs-ID 2 anger sessionsgränsen. Identifiera gränstypen från meddelandet och minska sedan samtidigheten, skala upp databasen eller förkorta långvariga åtgärder som innehåller resursen. |
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. |
Databasen överskrider sin garanterade miniminivå och den underliggande servern stryper prestandan. Återförsöket lyckas vanligtvis när grannbelastningen sjunker. Ihållande förekomster indikerar att du behöver en högre tjänstnivå eller en mindre bullrig miljö. |
40020, 40143, 40166, 40540 |
Rapporteras i platsen Error code %d för fel 40197 vid redundansväxling. |
Underkoder som är inbäddade i ett 40197-felväxlingsmeddelande och som vissa kodvägar visar som felnummer på översta nivån. Behandla dem på samma sätt som 40197. |
40197 |
The service has encountered an error processing your request. Please try again. Error code %d. |
En programvaruuppgradering, maskinvarufel eller annan redundanshändelse i Azure SQL. När du återansluter dirigeras du till en felfri replik. Den inbäddade felkoden identifierar redundanstypen. Om felet kvarstår samlar du in sessionsspårnings-ID:t och kontaktar supporten. |
40501 |
The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. |
Begränsning i Azure SQL-databasmotorn Den rekommenderade miniminivån är en väntetid på 10 sekunder. Ihållande strypning indikerar att arbetsbelastningen har överskridit databasens resursallokering; skala upp tjänstnivån eller minska samtidigheten. |
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'. |
Databasen är inte tillgänglig, vanligtvis under en redundansväxling eller en kort stund under en skalningsåtgärd. Försök igen vid en backoff; Om det kvarstår efter några minuter samlar du in sessionsspårnings-ID:t och öppnar ett supportärende. |
42108 |
Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. |
Den dedikerade SQL-poolen (Synapse) är i pausat tillstånd. Återförsöket lyckas först när poolen har återupptagits. Återuppta poolen uttryckligen, eller schemalägg arbetsbelastningen så att den körs efter att poolen har återupptagits. |
42109 |
The SQL pool is warming up. Please try again. |
Den dedikerade SQL-poolen håller på att återupptas. Försök igen vid en backoff tills poolen är online. uppvärmning tar vanligtvis några minuter. |
49918 |
Cannot process request. Not enough resources to process request. The service is currently busy. Please retry the request later. |
Servern kan för närvarande inte allokera tillräckligt med resurser för att uppfylla begäran. Försök igen vid en backoff. Om felet kvarstår skalar du upp databasen eller den elastiska poolen. |
49919 |
Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". |
Samtidighetsgräns på prenumerationsnivå för hanteringsoperationer. Minska antalet parallella anrop för att skapa eller uppdatera, eller fördela dem över tid. |
49920 |
Cannot process request. Too many operations in progress for subscription "%ld". |
Samtidighetsgräns på prenumerationsnivå för åtgärder under flygning. Minska graden av parallellism eller vänta tills pågående åtgärder har slutförts. |
Fel på instruktionsnivå finns inte i den här listan eftersom de utlöses när anslutningen har upprättats och felet lämnar sessionen användbar. De vanligaste återförsöksbara instruktionsfelen är 1205 (deadlock victim) och 1222 (tidsgräns för låsbegäran). Försök igen hela transaktionen i stället för den enda misslyckade instruktionen.
Felmeddelandetexten kommer från Azure SQL tillfälliga anslutningsfel. Enskilda drivrutiner underhåller sina egna inbyggda återförsökslistor. den här katalogen beskriver vilka fel som är berättigade till återförsök i SQL Server, Azure SQL Database, Azure SQL Managed Instance, SQL-databas i Microsoft Fabric och dedikerade SQL-pooler i Azure Synapse Analytics.
Följande isTransient funktion visar ett Go-implementationsmönster för återförsöksklassificering. Behandla den delade katalogen ovan som den enda sanningskällan, och håll ditt koduppslag anpassat efter den.
// 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
}
Om du upplever konfigurations- och kvotfel, åtgärda den underliggande kapaciteten, databasen eller nätverkskonfigurationen innan du försöker igen. Exempel är:
-
40544(databasstorlekskvot) -
4060(kan inte öppna databasen) -
40615(brandväggsregel)
Upptäck nätverksfel
Nätverksnivåfel ger mssql.Error inga värden. Kontrollera för vanliga typer av Go-nätverksfel:
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
}
Implementera återförsök med exponentiellt ökande väntetid
Försök igen med tillfälliga fel med ökande fördröjningar mellan försöken. Denna metod ger servern tid att återhämta sig och undviker att överväldiga den med snabba återförsök.
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)
}
Hantera dödlägen
Deadlocks (fel 1205) är det vanligaste tillfälliga felet i fleranvändarapplikationer. SQL Server avslutar automatiskt en av de konkurrerande sessionerna och returnerar fel 1205 till offret.
Upptäcka ett dödläge
Kontrollera om ett SQL Server-fel är en deadlock (fel 1205).
func isDeadlock(err error) bool {
var mssqlErr mssql.Error
if errors.As(err, &mssqlErr) {
return mssqlErr.Number == 1205
}
return false
}
Försök att köra transaktioner igen efter dödlägen
När en deadlock uppstår inom en transaktion rullar servern tillbaka hela transaktionen. Du måste försöka om hela transaktionen, inte bara det misslyckade påståendet:
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()
})
}
Tip
Minska deadlocks genom att komma åt tabeller i en konsekvent ordning över alla transaktioner och hålla transaktionerna korta.
Att försöka igen är rätt svar i applikationskoden, men upprepade deadlocks på samma fråga indikerar ett designproblem. Använd deadlock-grafen i SQL Server (som samlas in via Extended Events eller systemhälsosessionen) för att identifiera de konkurrerande instruktionerna och låstyperna. För en fullständig genomgång av deadlock-analys och förebyggande, se Deadlocks-guiden. För strategier för hantering av deadlock specifika för transaktioner, se Deadlock handling.
Hantera uttömning av anslutningspoolen
När alla anslutningar i poolen används och MaxOpenConns nås, blockeras nya anrop tills en anslutning blir tillgänglig eller tidsgränsen för kontexten löper ut. Denna situation yttrar sig som långsamma förfrågningar eller fel vid kontextdeadline, inte som explicita poolutmattningsfel.
Identifiera pooltryck
Övervaka poolstatistik och varna när vänteantalet ökar.
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)
}
}
}
}
Vanliga orsaker och lösningar
| Symptom | Orsak | Lösning |
|---|---|---|
WaitCount ökar stadigt |
MaxOpenConns är för låg |
Öka MaxOpenConns så att den matchar din nivå av samtidighet. |
InUse är lika med MaxOpenConns under längre perioder |
Anslutningar återförs inte till poolen | Stäng *sql.Rows, verkställ eller återställ *sql.Tx, och stäng *sql.Conn omgående. |
OpenConnections fortsätter att växa |
Anslutningar läcker snabbare än MaxIdleConns kan återvinna |
Ställ in ConnMaxLifetime och ConnMaxIdleTime för att ange gränser för anslutningens ålder. |
| Kontextdeadline överskriden under förfrågningar | Poolen är mättad och de som ringer väntar för länge | Öka poolstorleken, minska tiden för frågeexekvering eller lägg till frågetidsavvikelser. |
Hantera specifika SQL Server-fel
Begränsningsbrott
Unika nyckel- och främmande nyckelöverträdelser är permanenta fel som indikerar ett logikproblem i applikationen:
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
}
Upsert-mönster med identifiering av konflikter
Använd ett MERGE uttalande för att infoga eller uppdatera en rad atomärt:
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
}
Behörighetsfel
Upptäck vanliga felnummer som nekas behörigheter för att ge ett tydligt budskap till uppringare:
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
}
Hantera sql.ErrNoRows
sql.ErrNoRowsär inte ett SQL Server-fel. Metoden QueryRowContext.Scan returnerar den när frågan inte ger några rader. Hantera det uttryckligen för att skilja "inte hittad" från faktiska fel:
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
}
Lägg till sammanhang till fel
Lägg till kontext till fel så att anropare kan förstå var felet inträffade:
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
}
Att använda %w bevarar felkedjan så att anropare fortfarande kan använda errors.As och errors.Is för att inspektera det underliggande felet.
Checklista för felhantering
| Area | Recommendation |
|---|---|
| Typassertion | Deklarera var mssqlErr mssql.Error, och använd errors.As(err, &mssqlErr) sedan för att komma åt SQL Server-felfält. |
| Detektering av transienter | Klassificera fel efter antal och allvarlighetsgrad innan du bestämmer om du ska försöka igen. |
| Logik för återförsök | Använd exponentiell backoff med jitter. Ange ett maximalt antal försök och en övergripande tidsgräns via kontexten. |
| Deadlocks | Försök om hela transaktionen, inte enskilda kontoutdrag. Minska dödlägen genom att konsekvent få åtkomst till tabeller. |
| Poolutmattning | Övervaka db.Stats() och sätt kontextdeadlines för alla databasanrop. |
| ErrNoRows | Hantera sql.ErrNoRows uttryckligen för QueryRowContext. Det är inte ett serverfel. |
| Felinkapsling | Använd fmt.Errorf med %w för att lägga till kontext samtidigt som felkedjan bevaras. |
| Begränsningsbrott | Kontrollera felnummer 2627, 2601 (unik) och 547 (främmande nyckel) för att hantera konflikter smidigt. |