mssql-django ile mantığı ve bağlantı dayanıklılığını yeniden deneyin

SQL Server ve Azure SQL bağlantıları, kodunuzla hiçbir ilgisi olmayan nedenlerle geçici olarak başarısız olabilir:

  • Bir Always On kullanılabilirlik grubu yük devreder.
  • Ağ, bağlantı kurulumu sırasında bir paket bırakır.
  • Resource Governor veritabanını sınırlandırır.
  • Bir Azure SQL replikası, ölçeklendirme veya yükseltme sırasında yeniden oluşturulur.

Bu hataların çoğu saniyeler içinde temizlir. Bu makalede, mssql-django arka ucunu kullanan bir Django uygulamasında geçici hataların nasıl yeniden deneneceği ve Django ile ODBC sürücüsünün boşta kalan bağlantıların kopmasından otomatik olarak kurtulacak şekilde nasıl yapılandırılacağı anlatılmaktadır.

Geçici hatalar

Geçici hatalar, kendi kendine çözülen geçici hatalardır. Kısa bir gecikmeden sonra işlemi yeniden denemek genellikle başarılı olur.

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.

ODBC sürücüsü boşta bağlantı dayanıklılığı

SQL Server için Microsoft ODBC Sürücüsü, ConnectRetryCount ve ConnectRetryInterval bağlantı dizesi anahtar sözcükleriyle yerleşik boşta bağlantı dayanıklılığı sunar. Bu ayarlar, uygulama kodunuz devreye girmeden önce, sürücü düzeyinde boşta kalan ve kesilen bağlantıları ele alır.

içinde extra_paramsboşta bağlantı dayanıklılığını etkinleştirin:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>.database.windows.net",
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "ConnectRetryCount=3;ConnectRetryInterval=10",
        },
    },
}
Keyword Varsayılan Description
ConnectRetryCount 1 Boşta bağlantılar için otomatik yeniden bağlantı denemelerinin sayısı.
ConnectRetryInterval 10 Yeniden bağlanma girişimleri arasındaki saniye sayısı.

Note

Boştaki bağlantı dayanıklılığı, boştayken kopan bağlantıları yeniden kurar. Başarısız sorguları yeniden denemez veya etkin işlemler sırasında oluşan hatalardan kurtarmaz. Bu senaryolar için uygulama düzeyinde yeniden deneme mantığını kullanın.

Yeniden denemeler için Django veritabanı ara yazılımı

Geçici hataları yakalayan ve veritabanı işlemini yeniden denenen bir Django ara yazılımı oluşturun. Bu yaklaşım, görünüm düzeyinde istek işleme için çalışır:

# myproject/middleware.py
import random
import re
import time
import logging
from django.db import OperationalError, connection

logger = logging.getLogger(__name__)

TRANSIENT_ERROR_CODES = {
    "64", "233", "4221",
    "10053", "10054", "10928", "10929",
    "40197", "40501", "40613",
    "49918", "49919", "49920",
    # Include "4060" only if targeting Azure SQL with geo-replication failover.
    # It is usually a permanent error (wrong database name or missing permissions).
}

# Microsoft ODBC driver formats native error codes as "(<number>)" in the
# message. Extracting parenthesized codes avoids false positives that a plain
# substring match would produce for short codes like "64".
_CODE_RE = re.compile(r"\((\d+)\)")


def is_transient(error):
    codes_in_message = set(_CODE_RE.findall(str(error)))
    return bool(codes_in_message & TRANSIENT_ERROR_CODES)


class DatabaseRetryMiddleware:
    """Retry database operations on transient errors."""

    def __init__(self, get_response):
        self.get_response = get_response
        self.max_retries = 3
        self.base_delay = 1   # seconds; doubled each attempt
        self.max_delay = 30   # cap on a single sleep, regardless of attempt

    def __call__(self, request):
        for attempt in range(self.max_retries + 1):
            try:
                return self.get_response(request)
            except OperationalError as e:
                if attempt < self.max_retries and is_transient(e):
                    # Exponential backoff with full jitter, capped at max_delay.
                    # Jitter spreads simultaneous retries so many clients
                    # don't hammer the server in lock-step during an outage.
                    capped = min(self.max_delay, self.base_delay * (2 ** attempt))
                    delay = random.uniform(0, capped)
                    logger.warning(
                        "Transient DB error (attempt %d/%d), retrying in %.2fs: %s",
                        attempt + 1, self.max_retries, delay, e
                    )
                    connection.close()
                    time.sleep(delay)
                    continue
                raise

Ara yazılımını settings.py içine kaydedin:

MIDDLEWARE = [
    "myproject.middleware.DatabaseRetryMiddleware",
    "django.middleware.security.SecurityMiddleware",
    # ... other middleware
]

Important

DatabaseRetryMiddleware Veritabanına erişen diğer ara yazılımların önüne yerleştirerek istek işlem hattının tamamından geçici hataları yakalayıp yeniden denemesini sağlayın.

Belirli işlemler için yeniden deneme dekoratörü

Daha ayrıntılı denetim için tek tek işlevlerde bir dekoratör kullanın:

import random
import re
import time
import functools
import logging
from django.db import OperationalError, connection

logger = logging.getLogger(__name__)

TRANSIENT_ERROR_CODES = {
    "64", "233", "4221",
    "10053", "10054", "10928", "10929",
    "40197", "40501", "40613",
    "49918", "49919", "49920",
    # Include "4060" only if targeting Azure SQL with geo-replication failover.
}

_CODE_RE = re.compile(r"\((\d+)\)")


def is_transient(error):
    codes_in_message = set(_CODE_RE.findall(str(error)))
    return bool(codes_in_message & TRANSIENT_ERROR_CODES)


def retry_on_transient(max_retries=3, base_delay=1, max_delay=30):
    """Retry on transient database errors with exponential backoff and full jitter."""

    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries + 1):
                try:
                    return func(*args, **kwargs)
                except OperationalError as e:
                    if attempt < max_retries and is_transient(e):
                        # Exponential cap doubled per attempt, then jittered
                        # within [0, cap] and limited by max_delay.
                        capped = min(max_delay, base_delay * (2 ** attempt))
                        delay = random.uniform(0, capped)
                        logger.warning(
                            "Transient error in %s (attempt %d/%d), retrying in %.2fs: %s",
                            func.__name__, attempt + 1, max_retries, delay, e
                        )
                        connection.close()
                        time.sleep(delay)
                        continue
                    raise
        return wrapper
    return decorator

Dekoratör'leri veritabanı ağırlıklı işlevlere uygulayın:

from myproject.retry import retry_on_transient

@retry_on_transient(max_retries=3, base_delay=2)
def process_order(order_id):
    """Process an order with automatic retry on transient failures."""
    order = Order.objects.select_for_update().get(id=order_id)
    order.status = "processing"
    order.save()
    return order

İşlemlerle yeniden deneme

Bir işlemin içinde geçici bir hata oluştuğunda, işlemin tamamı sunucu tarafından geri alınır. Yalnızca başarısız olan ifadeyi değil, işlemin tamamını yeniden deneyin:

from django.db import transaction

@retry_on_transient(max_retries=3)
def transfer_funds(from_account_id, to_account_id, amount):
    """Transfer funds between accounts with retry."""
    with transaction.atomic():
        from_account = Account.objects.select_for_update().get(id=from_account_id)
        to_account = Account.objects.select_for_update().get(id=to_account_id)

        from_account.balance -= amount
        to_account.balance += amount

        from_account.save()
        to_account.save()

Caution

transaction.atomic() içinde yeniden denemeyin. Yeniden deneme dekoratörü, her yeniden denemenin yeni bir işlem başlatması için atomic() bloğunun tamamını sarmalamalıdır.

Deyim düzeyi hataları

Önceki bölümdeki hata listesi bağlantı düzeyi hatalarını kapsar. Diğer iki hata genellikle deyim düzeyinde yeniden denenmektedir:

  • 1205: Oturum kilitlenme kurbanı olarak seçildi. İşlemi yeniden çalıştırın.
  • 1222: Kilit isteğinin zaman aşımı süresi aşıldı. İşlemi yeniden çalıştırın veya varsayılan değer çok agresifse oturum için artırın LOCK_TIMEOUT .

ConnectRetryCount kopan bağlantıları yeniden dener, bu nedenle ifade düzeyindeki bu hatalara uygulanmaz. Yeniden çalıştırılması güvenli işlemler için, TRANSIENT_ERROR_CODES öğesine "1205" ve "1222" ekleyerek bunları aynı dekoratör deseniyle işleyin.

CONN_MAX_AGE ve bayat bağlantılar

Django, CONN_MAX_AGE ayarlandığında istekler arasında veritabanı bağlantılarını yeniden kullanır. Uzun süreli bir bağlantı, sunucu bağlantıyı kapatırsa (örneğin, bir Azure SQL ölçeklendirme işlemi veya güvenlik duvarı zaman aşımı sırasında) geçersiz hale gelebilir.

Yeniden kullanımı eskimeye karşı dengelemek için ayarlayın CONN_MAX_AGE :

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>.database.windows.net",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
        "CONN_MAX_AGE": 600,  # Close and reopen connections after 10 minutes
    },
}
  • CONN_MAX_AGE=0 (varsayılan): Her isteğin sonundaki bağlantıyı kapatın. En güvenli ama en yavaş.
  • CONN_MAX_AGE=600: Bağlantıları 10 dakika boyunca yeniden kullan. Çoğu web uygulaması için iyi denge.
  • CONN_MAX_AGE=None: Bağlantıları süresiz olarak açık tutun. Yalnızca zaman aşımına uğramış bağlantılar için yeniden deneme mekanizması bulunan durumlarda kullanın.

CONN_HEALTH_CHECKS (Django 4.1 ve üzeri)

Django 4.1, her istekten önce yeniden kullanılan bir bağlantıyı doğrulayan CONN_HEALTH_CHECKS özelliğini kullanıma sundu. Eski bağlantıları otomatik olarak algılamak için bunu CONN_MAX_AGE ile birlikte etkinleştirin:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>.database.windows.net",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
        "CONN_MAX_AGE": 600,
        "CONN_HEALTH_CHECKS": True,
    },
}

Sistem durumu denetimleri etkinleştirildiğinde Django, bağlantıyı yeniden kullanmadan önce basit bir doğrulama sorgusu oluşturur. Bağlantı kesilirse, Django hata oluşturmak yerine şeffaf bir şekilde yeni bir bağlantı açar.

En iyi uygulamalar

  • Tam jitter ile üstel geri çekilme kullanın. Her denemede üst sınırı ikiye katlayın, ardından [0, cap] içinde rastgele bir süre bekleyin. Rastgele gecikme, bölgesel bir kesinti sırasında birçok istemcinin eşzamanlı olarak yeniden deneme yapmasını önler; aksi takdirde bu durum kısa süreli bir arızayı kalıcı bir aşırı yük durumuna dönüştürebilir. Toplam kurtarma süresinin sınırlanmış olarak kalması için deneme başına uyku süresini (örneğin, 30 saniye) sınırla.
  • Yeniden deneme tavanı ayarlayın. Üstel geri çekilme ile üç yeniden deneme, uygun bir varsayılan ayardır. Beşten fazla yeniden deneme genellikle geçici olmayan bir sorunu gösterir.
  • Yeniden denemeden önce bağlantıyı kapatın. Django bir sonraki denemede yeni bir bağlantı açsın diye ara connection.close() .
  • Her yeniden denemeyi günlüğe kaydetme. Fark ettirmeden başarıyla sonuçlanan yeniden denemeler performans sorunlarını gizleyebilir. Sıklığı takip edebilmeniz için WARNING düzeyinde günlük kaydı tutun.
  • Geçici olmayan hataları yeniden denemeyin. Kimlik doğrulama hataları, izin hataları ve söz dizimi hataları yeniden denemelerden yararlanamaz.
  • İşlemin tamamını yeniden deneyin. transaction.atomic() öğesini tersi değil, yeniden deneme mantığının içine sarın.
  • CONN_HEALTH_CHECKS özelliğini etkinleştirin (CONN_MAX_AGE kullanan web uygulamaları için, Django 4.1 ve sonrası).