Go-mssqldb ile hata işleme ve tekrar deneme kalıpları

Production Go uygulamaları, tekrar deneyebileceğiniz geçici hatalar ile insan müdahalesi gerektiren kalıcı hataları ayırt etmek için yapılandırılmış hata işleme gerektirir. Bu makale, hata sınıflandırması, tekrar deneme kalıpları ve sürücü go-mssqldb için dayanıklılık stratejilerini ele alır.

SQL Server hata yapısı

SQL Server hata döndürdüğünde, go-mssqldb sürücü hatayı bir mssql.Error struct içinde sarar. Yapılandırılmış hata alanlarına erişmek için bir tür onayı kullanın:

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

Hata alanları

Alan Türü Description
Number int32 SQL Server hata numarası. sys.messagesile eşler.
State uint8 Hata durumu. Aynı hata sayısı için ek bağlam sağlar.
Class uint8 Şiddet seviyesi (0-25). 11-16 şiddet dereceleri kullanıcı tarafından düzeltilebilir. 17+ şiddeti kaynak veya sistem sorunlarını gösterir.
Message string Sunucudan insan tarafından okunabilir hata metni.
ServerName string Hatayı ortaya çıkaran SQL Server örneğinin adı.
ProcName string Hatanın gerçekleştiği saklanan prosedür veya fonksiyon adı. Ad hoc sorgular için boştur.
LineNo int32 Transact-SQL (T-SQL) toplu veya depolanmış prosedürdeki satır numarası.

Ciddiyet seviyeleri

Şiddet aralığı Meaning Action
0-10 Bilgilendiren iletiler Hata yok. Faydalıysa kayıt yap.
11-16 Kullanıcı tarafından düzeltilebilen hatalar Sorgu, parametreler veya izinleri düzeltin.
17-19 Kaynak hataları Yeniden deneyin. Sunucu yük altında olabilir veya kaynakları tükenmiş olabilir.
20-25 Ölümcül hatalar Bağlantı kesildi. Yeniden bağlan ve tekrar dene.

Hataları geçici veya kalıcı olarak sınıflandırın

Geçici hatalar, ağ blips'leri, bağlantı kısıtlaması veya kısa süreli kaynak çatışması gibi kendi kendine çözülen geçici durumlardır. Kalıcı hatalar kod veya yapılandırma değişiklikleri gerektirir.

Yaygın geçici hata sayıları

Aşağıdaki paylaşılan kataloğu, geçici bağlantı kurma ve istek yolu taşıma hatalarının kanonik listesi olarak kullanın:

Aşağıdaki hatalar, bağlantı kurulması sırasında veya sunucuya istek gönderilirken ortaya çıktığında geçicidir. Kısa, sınırlanmış geri çekilmeyi yeniden deneyin. Birkaç yeniden denemeden sonra kalıcı olan hatalar genellikle yeniden denemenin düzeltmeyeceği bir yapılandırma sorununu (yanlış sunucu, eksik izinler, tükenmiş kota) gösterir.

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 bağlantısı el sıkışma işleminin ortasında kesilir. Kimlik bilgileriyle ilgili bir hata değil. Sorun devam ederse, istemci tarafındaki ağ kararsızlığını veya yarım kurulmuş bağlantıları sonlandıran bir ara cihaz olup olmadığını kontrol edin.
233 The client was unable to establish a connection because of an error during connection initialization process before login. Oturum açma öncesi aktarım veya TLS hatası. Sunucu genellikle bağlantıyı kabul etmediğinde (kaynak tükenmesi, maksimum bağlantılara ulaşılması veya desteklenmeyen bir istemci) döndürür. Kimlik bilgileriyle ilgili bir hata değil. Sunucu durumunu doğrulayın, ardından istemci oturum açma zaman aşımını, TLS ayarlarını ve istemci/sunucu TLS sürüm uyumluluğunu denetleyin.
4060 Cannot open database "%.*ls" requested by the login. The login failed. Oturum açma kimliği doğrulanır ancak istenen veritabanını açamaz. Geçici nedenler arasında veritabanının bir geçiş durumunda olması (yük devretme, geri yükleme, ölçeklendirme) veya otomatik olarak duraklatılmış olması yer alır. Kalıcı nedenler (veritabanı mevcut değil, oturum açma erişimi yok) yeniden denenerek düzeltilmez; veritabanı adını, oturum açma eşlemesini ve veritabanı durumunu denetleyin.
4221 Login to read-secondary failed due to long wait on 'HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING'. Çoğaltma geri dönüştürüldükleri sırada uçuşta olan işlemler için satır sürümleri eksik olduğundan çoğaltma oturum açma için kullanılamaz. Sorunu çözmek için birincil üzerindeki aktif işlemleri geri alın veya onaylayın. Birincil üzerinde uzun yazma işlemlerinden kaçınarak etkisini azaltın.
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.) Yerel taraf bağlantıyı durdurur. İstemci tarafı ağ durumunu ve herhangi bir yerel güvenlik duvarını veya VPN istemcilerini denetleyin.
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.) Uzak taraf bir TCP sıfırlaması gönderir. Yaygın nedenler: eş süreç çöktü, bir güvenlik duvarı bir sıfırlama paketi gönderdi veya Azure SQL ağ geçidi boşta olan bir bağlantıyı kapattı. Bağlantının boştayken sıfırlanması durumlarında, istemcide TCP keepalive’ı etkinleştirin veya bağlantı havuzundaki boşta kalma zaman aşımını kısaltın.
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. Veritabanı Azure SQL kaynak idare sınırını aşıyor. Kaynak Kimliği 1, çalışan sınırını gösterir; Kaynak Kimliği 2, oturum sınırını gösterir. İletideki sınır türünü belirleyin, ardından eşzamanlılığı azaltın, veritabanının ölçeğini genişletin veya kaynağı tutan uzun süre çalışan işlemleri kısaltın.
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. Veritabanı minimum garanti düzeyini aştı ve altyapıdaki sunucu kısıtlama uyguluyor. Yeniden deneme genellikle komşu yükü düştüğünde başarılı olur. Sürekli oluşumlar, daha yüksek bir hizmet katmanına veya daha az gürültülü bir ortama ihtiyacınız olduğunu gösterir.
40020, 40143, 40166, 40540 Yük devretme sırasında oluşan 40197 hatasının Error code %d alanında bildirildi. Bazı yolların üst düzey hata numarası olarak göründüğü 40197 yük devretme iletisine eklenmiş alt kodlar. Bunları 40197 ile aynı şekilde değerlendirin.
40197 The service has encountered an error processing your request. Please try again. Error code %d. Azure SQL’de bir yazılım yükseltmesi, donanım arızası veya başka bir yük devretme durumu. Yeniden bağlanma sizi sağlıklı bir kopyaya yönlendirir. Eklenen hata kodu yük devretme türünü tanımlar. Hata devam ederse oturum izleme kimliğini yakalayın ve desteğe başvurun.
40501 The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. Azure SQL motor azaltma. Önerilen minimum bekleme süresi 10 saniyedir. Sürekli kısıtlama, iş yükünün veritabanının kaynak tahsisini aştığını gösterir; hizmet katmanını yükseltin veya eşzamanlılığı azaltın.
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'. Veritabanı, genellikle yük devretme sırasında veya ölçeklendirme işlemi sırasında kısa bir süreliğine kullanılamaz. Artan aralıklarla yeniden deneyin; sorun birkaç dakikadan uzun sürerse oturum izleme kimliğini kaydedin ve bir destek kaydı oluşturun.
42108 Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. Ayrılmış SQL havuzu (Synapse) duraklatılmış durumda. Yeniden deneme yalnızca havuz yeniden etkinleştirildikten sonra başarılı olur. Havuzu açıkça yeniden etkinleştirin veya iş yükünü, havuz yeniden etkinleştirildikten sonra çalışacak şekilde zamanlayın.
42109 The SQL pool is warming up. Please try again. Ayrılmış SQL havuzu yeniden başlatılıyor. Havuz çevrimiçi olana kadar geri çekilmeyi yeniden deneyin; ısınma genellikle birkaç dakika sürer.
49918 Cannot process request. Not enough resources to process request. The service is currently busy. Please retry the request later. Sunucu şu anda isteği karşılamak için yeterli kaynak ayıramıyor. Geri alma işlemini yeniden deneyin. Hata devam ederse veritabanının veya elastik havuzun ölçeğini büyütün.
49919 Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". Yönetim işlemlerinde abonelik düzeyinde eşzamanlılık sınırı. Paralel oluşturma/güncelleştirme çağrılarını azaltın veya bunları kademeleyin.
49920 Cannot process request. Too many operations in progress for subscription "%ld". Uçuştaki işlemlerde abonelik düzeyinde eşzamanlılık sınırı. Paralelliği azaltın veya uçuş içi işlemlerin boşalmasını bekleyin.

İfade düzeyindeki hatalar, bağlantı kurulduktan sonra ortaya çıktıkları ve hata sonrasında oturum kullanılabilir durumda kaldığı için bu listede yer almıyor. En yaygın yeniden denenebilir ifade hataları 1205 (deadlock kurbanı) ve 1222'dir (kilit isteği zaman aşımı). Başarısız olan tek bir komut yerine işlemin tamamını yeniden deneyin.

Hata iletisi metni Azure SQL geçici bağlantı hatalarından kaynaklanır. Her sürücü kendi yerleşik yeniden deneme listesine sahiptir; bu katalog, SQL Server, Azure SQL Veritabanı, Azure SQL Yönetilen Örneği, Microsoft Fabric’teki SQL veritabanı ve Azure Synapse Analytics’teki ayrılmış SQL havuzları genelinde hangi hataların yeniden deneme için uygun olduğunu açıklar.

Aşağıdaki isTransient fonksiyon, yeniden deneme sınıflandırması için bir Go uygulama desenini gösterir. Yukarıdaki paylaşılan kataloğu gerçeğin kaynağı olarak ele alın ve kod aramanızı onunla uyumlu tutun.

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

Yapılandırma ve kota hataları yaşarsanız, yeniden denemeden önce altta yatan kapasite, veritabanı veya ağ yapılandırmasını düzeltin. Örnekler şunları içerir:

  • 40544 (veritabanı büyüklüğü kotası)
  • 4060 (veritabanı açılamıyor)
  • 40615 (güvenlik duvarı kuralı)

Ağ hatalarını tespit et

Ağ düzeyindeki hatalar değer üretmez mssql.Error . Yaygın Go ağı hata türlerini kontrol edin:

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
}

Üstel geri dönüşle yeniden deneme uygula

Geçici hataları, denemeler arasında giderek artan gecikmelerle yeniden deneyin. Bu yaklaşım, sunucuya toparlanma zamanı verir ve hızlı denemelerle onu bunaltmaktan kaçınır.

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

Kilitlenmeleri ele alma

Deadlocks (hata 1205), çok kullanıcılı uygulamalarda en yaygın geçici hatadır. SQL Server, rakip oturumlardan birini otomatik olarak sonlandırır ve mağdura 1205 numaralı hatayı döndürür.

Kilitlenmeyi algıla

SQL Server hatasının çıkmaz olup olmadığını kontrol edin (hata: 1205).

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

Kilitlenmelerden sonra işlemleri yeniden deneyin

Bir işlem sırasında bir kilitlenme oluştuğunda, sunucu işlemin tamamını geri alır. Sadece başarısız ifadeyi değil, tüm işlemi tekrar denemeniz gerekir:

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()
    })
}

İpucu

Tüm işlemlerde tutarlı bir sırayla tablolara erişerek ve işlemleri kısa tutarak çıkmazları azaltın.

Uygulama kodunda tekrar denemek doğru yanıttır, ancak aynı sorguda tekrar tekrar çıkmak bir tasarım sorunu olduğunu gösterir. SQL Server deadlock grafiğini (Genişletilmiş Olaylar veya sistem sağlığı oturumu aracılığıyla yakalanan) kullanarak rakip ifadeleri ve kilit türlerini belirleyebilirsiniz. Deadlock analizi ve önleme konularında tam bir rehber için Deadlocks rehberine bakınız. İşlemlere özgü deadlock yönetimi stratejileri için bkz. Deadlock handleling.

Bağlantı havuzu tükenmesini ele alın

Havuzdaki tüm bağlantılar kullanımdayken ve MaxOpenConns sınırına ulaşıldığında, yeni çağıranlar bir bağlantı kullanılabilir hale gelene veya context süresi dolana kadar bekler. Bu durum, açıkça belirtilen havuz tükenmesi hataları olarak değil, yavaş istekler veya bağlam zaman aşımı hataları şeklinde kendini gösterir.

Havuz basıncını tespit et

Havuz istatistiklerini takip edin ve bekleme sayısı arttığında uyarın.

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

Yaygın nedenler ve çözümler

Belirti Cause Çözüm
WaitCount sürekli artar MaxOpenConns çok düşük MaxOpenConns değerini eşzamanlılık düzeyinize göre artırın.
InUse, uzun süre boyunca MaxOpenConns ile eşit kalır Bağlantılar havuza geri verilmiyor *sql.Rows öğesini kapatın, *sql.Tx işlemini onaylayın veya geri alın ve *sql.Conn öğesini hemen kapatın.
OpenConnections büyümeye devam ediyor Bağlantılar, MaxIdleConns geri dönüştürebilenden daha hızlı sızar ConnMaxLifetime ve ConnMaxIdleTime öğelerini bağlantı yaşı sınırını ayarlayacak şekilde ayarlayın.
Sorgular sırasında bağlam son tarihi aşıldı Havuz doymuş ve arayanlar çok uzun süre bekliyor Havuz büyüklüğünü artırın, sorgu yürütme süresini azaltın veya sorgu zaman aşımları ekleyin.

Belirli SQL Server hatalarını ele alın

Kısıtlama ihlalleri

Benzersiz anahtar ve yabancı anahtar ihlalleri, uygulamada mantık sorunu olduğunu gösteren kalıcı hatalardır:

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
}

Çatışma tespitli upsert örüntüsü

Bir satırı atomik olarak eklemek veya güncellemek için bir MERGE ifade kullanın:

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
}

İzin hataları

Arayanlara net bir mesaj sağlamak için yaygın izin reddedilen hata numaralarını tespit edin:

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
}

sql.ErrNoRows hatasını ele al

sql.ErrNoRowsSQL Server hatası değil. QueryRowContext.Scan yöntemi, sorgu hiç satır döndürmediğinde onu döndürür. "Bulunamadı" ile gerçek hataları ayırt etmek için bunu açıkça ele alın:

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
}

Hataları bağlamla sarmala

Hatalara bağlam ekleyin ki arayanlar arızanın nerede gerçekleştiğini anlayabilsin:

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
}

%w kullanmak, hata zincirini korur; böylece çağıranlar, altta yatan hatayı incelemek için yine de errors.As ve errors.Is kullanabilir.

Hata yönetimi kontrol listesi

Area Recommendation
Tür belirtme var mssqlErr mssql.Error bildirimini yapın, ardından SQL Server hata alanlarına erişmek için errors.As(err, &mssqlErr) kullanın.
Geçici algılama Hataları tekrar denemeye karar vermeden önce sayıya ve ciddiyete göre sınıflandırın.
Mantığı yeniden deneme Üstel geri çekilme ile rastgelelik kullanın. Bağlam aracılığıyla maksimum deneme sayısını ve toplam zaman aşımı süresini ayarlayın.
Deadlocks Tek tek komutları değil, işlemin tamamını yeniden deneyin. Tablolara tutarlı şekilde erişerek çıkmazları azaltın.
Havuz tükenmesi db.Stats() İzleyin ve tüm veritabanı çağrıları için bağlam zaman aşımı süreleri ayarlayın.
ErrNoRows QueryRowContext için sql.ErrNoRows öğesini açıkça işleyin. Bu bir sunucu hatası değil.
Hata sarma Hata zincirini korurken bağlam eklemek için fmt.Errorf ile birlikte %w kullanın.
Kısıtlama ihlalleri Çatışmaları zarifçe yönetmek için 2627, 2601 (tekiz) ve 547 (yabancı anahtar) hata numaralarını kontrol edin.