Újrapróbálkozási logika és kapcsolati rugalmasság az mssql-django használatával

Az SQL Serverhez és az Azure SQL-hez való kapcsolatok a kódjától független okok miatt átmenetileg meghiúsulhatnak:

  • Az Always On rendelkezésre állási csoport feladatátvételt hajt végre.
  • A hálózat a kapcsolat beállítása során elvet egy csomagot.
  • A Resource Governor korlátozza az adatbázist.
  • A Azure SQL replika skálázás vagy frissítés során lesz újrahasznosítva.

A hibák többsége másodperceken belül törlődik. Ez a cikk bemutatja, hogyan lehet újrapróbálni átmeneti hibák esetén egy mssql-django háttérrendszert használó Django-alkalmazásban, és hogyan konfigurálható a Django és az ODBC-illesztő úgy, hogy automatikusan helyreálljanak a tétlen kapcsolatok megszakadása után.

Átmeneti hibák

Az átmeneti hibák átmeneti hibák, amelyek önállóan oldódnak meg. A művelet rövid késleltetés után történő újrapróbálkozása általában sikeres lesz.

A következő hibák átmenetiek, amikor a kapcsolat létrehozásakor vagy a kiszolgálónak küldött kérések során fordulnak elő. Próbálkozzon újra rövid, korlátozott várakozási idő után. A néhány újrapróbálkozást követő hibák általában olyan konfigurációs problémát jeleznek (hibás kiszolgáló, hiányzó engedélyek, kimerült kvóta), amely az újrapróbálkozást nem oldja meg.

Hiba 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.) A TCP-kapcsolat kézfogás közben megszakad. Ez nem hitelesítési hiba. Ha továbbra is fennáll, ellenőrizze az ügyféloldali hálózati instabilitást, vagy egy olyan köztes eszközt, amely megszakítja a félig létrehozott kapcsolatokat.
233 The client was unable to establish a connection because of an error during connection initialization process before login. Bejelentkezés előtti átvitel vagy TLS-hiba. A kiszolgáló általában akkor adja vissza, ha nem tudja elfogadni a kapcsolatot (erőforrás-kimerültség, elért maximális kapcsolatok vagy nem támogatott ügyfél). Ez nem hitelesítési hiba. Ellenőrizze a kiszolgáló állapotát, majd ellenőrizze az ügyfél bejelentkezési időtúllépését, a TLS-beállításokat és az ügyfél/kiszolgáló TLS-verziókompatibilitását.
4060 Cannot open database "%.*ls" requested by the login. The login failed. A bejelentkezés hitelesít, de nem tudja megnyitni a kért adatbázist. Az átmeneti okok közé tartozik, ha az adatbázis átmeneti állapotban van (feladatátvétel, visszaállítás, skálázás miatt), vagy automatikusan szüneteltetve van. Az állandó okok (az adatbázis nem létezik, a bejelentkezés nem rendelkezik hozzáféréssel) nem lesznek javítva újrapróbálkozással; ellenőrizze az adatbázis nevét, a bejelentkezési leképezést és az adatbázis állapotát.
4221 Login to read-secondary failed due to long wait on 'HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING'. A replika nem érhető el bejelentkezéshez, mert a sorverziók hiányoznak a replika újrafeldolgozásakor repülés közben végrehajtott tranzakciókhoz. A probléma megoldásához vonja vissza vagy véglegesítse az aktív tranzakciókat az elsődleges kiszolgálón. Csökkentse ezt azzal, hogy kerüli a hosszú írási tranzakciókat az elsődleges csomóponton.
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.) A helyi oldal megszakítja a kapcsolatot. Ellenőrizze az ügyféloldali hálózat állapotát, valamint a helyi tűzfal vagy VPN-ügyfél állapotát.
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.) A távoli oldal TCP-alaphelyzetbe állítást küld. Gyakori okok: a partnerfolyamat összeomlott, egy tűzfal resetet küldött, vagy az Azure SQL-átjáró lezárt egy inaktív kapcsolatot. Tétlenség miatti kapcsolatbontási minták esetén engedélyezze a TCP keepalive funkciót az ügyféloldalon, vagy csökkentse a kapcsolatpool tétlenségi időkorlátját.
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. Az adatbázis túllépi a Azure SQL erőforrás-szabályozási korlátot. Az 1. erőforrás-azonosító a feldolgozói korlátot jelzi; A 2. erőforrás-azonosító a munkamenetkorlátot jelzi. Azonosítsa az üzenet korláttípusát, majd csökkentse az egyidejűséget, skálázza fel az adatbázist, vagy lerövidítse az erőforrást tartalmazó hosszú ideig futó műveleteket.
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. Az adatbázis meghaladja a számára garantált minimumot, és a mögöttes kiszolgáló korlátozza a teljesítményét. Az újrapróbálkozás általában akkor sikeres, ha a szomszéd terhelés csökken. A tartós előfordulások azt jelzik, hogy magasabb szolgáltatási szintre vagy kevésbé zajos környezetre van szüksége.
40020, 40143, 4016640540 Error code %d A 40197-es hibahelyen jelentve a feladatátvétel során. A 40197-es feladatátvételi üzenetbe beágyazott alkódok, amelyek egyes útvonalakon legfelső szintű hibaszámként jelennek meg. Kezelje őket ugyanúgy, mint a 40197-et.
40197 The service has encountered an error processing your request. Please try again. Error code %d. Szoftverfrissítés, hardverhiba vagy más feladatátvételi esemény az Azure SQL-ben. Az újracsatlakozás egy működőképes replikához kapcsol. A beágyazott hibakód azonosítja a feladatátvétel típusát. Ha a hiba továbbra is fennáll, rögzítse a munkamenet-nyomkövetés azonosítóját, és forduljon az ügyfélszolgálathoz.
40501 The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. Azure SQL motor szabályozása. Az ajánlott minimális várakozási idő 10 másodperc. A tartós korlátozás azt jelzi, hogy a munkaterhelés túllépte az adatbázis erőforrás-kiosztását; növelje a szolgáltatási szintet, vagy csökkentse a párhuzamosságot.
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'. Az adatbázis nem érhető el, általában feladatátvétel alatt vagy rövid ideig egy skálázási művelet közben. Próbálkozzon újra egy visszalépéssel; ha néhány perc elteltével is megmarad, rögzítse a munkamenet nyomkövetési azonosítóját, és nyisson meg egy támogatási esetet.
42108 Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. A dedikált SQL-készlet (Synapse) szüneteltetett állapotban van. Az újrapróbálás csak a készlet újraindítása után sikerül. Állítsa vissza explicit módon a készletet működő állapotba, vagy ütemezze a munkaterhelés futtatását a készlet folytatását követő időpontra.
42109 The SQL pool is warming up. Please try again. A dedikált SQL-készlet újra működik. Próbálkozzon újra egyre hosszabb várakozási időkkel, amíg a pool online nem lesz; a bemelegítés általában néhány percet vesz igénybe.
49918 Cannot process request. Not enough resources to process request. The service is currently busy. Please retry the request later. A kiszolgáló jelenleg nem tud elegendő erőforrást lefoglalni a kérés teljesítéséhez. Próbálkozzon újra egy visszalépéssel. Ha a hiba továbbra is fennáll, skálázza fel az adatbázist vagy a rugalmas készletet.
49919 Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". Előfizetési szintű egyidejűségi korlát a felügyeleti műveletekre. Csökkentse a párhuzamos létrehozási vagy frissítési hívások számát, vagy időben tolja el őket.
49920 Cannot process request. Too many operations in progress for subscription "%ld". Előfizetésszintű egyidejűségi korlát a repülési műveletekre. Csökkentse a párhuzamosságot, vagy várja meg, amíg a folyamatban lévő műveletek befejeződnek.

Az utasításszintű hibák nem szerepelnek ezen a listán, mert a kapcsolat létrejötte után jelentkeznek, és a hiba ellenére a munkamenet továbbra is használható marad. A leggyakoribb újrapróbálkozható utasításhibák az 1205 (holtpont áldozata) és az 1222 (zárolási kérelem időtúllépése). Az egyetlen sikertelen utasítás helyett próbálkozzon újra a teljes tranzakcióval.

A hibaüzenet szövege Azure SQL átmeneti kapcsolati hibákból származik. Az egyes illesztőprogramok saját beépített újrapróbálkozási listákat tartanak fenn; ez a katalógus azt írja le, hogy mely hibák alkalmasak az újrapróbálkozásra az SQL Server, az Azure SQL Database, az Azure SQL Managed Instance, a Microsoft Fabric SQL-adatbázisa és az Azure Synapse Analytics dedikált SQL-készletei esetén.

Az ODBC-illesztőprogram üresjárati kapcsolatainak hibatűrése

A Microsoft SQL Serverhez készült ODBC-illesztőprogram beépített helyreállítási képességet biztosít az inaktív kapcsolatokhoz a ConnectRetryCount kapcsolati sztring ConnectRetryInterval kulcsszavaival. Ezek a beállítások az inaktív, megszakadt kapcsolatokat az illesztőprogram szintjén kezelik, még mielőtt az alkalmazás kódja érintetté válna.

Az üresjárati kapcsolatok hibaállóságának engedélyezése a(z) extra_params esetében:

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 Alapértelmezett Description
ConnectRetryCount 1 Tétlen kapcsolatok automatikus újracsatlakozási kísérleteinek száma.
ConnectRetryInterval 10 Másodperc az újracsatlakozási kísérletek között.

Note

Az inaktív kapcsolat rugalmassága újracsatlakoztat olyan kapcsolatokat, amelyek tétlen állapotban megszakadtak. Nem próbálkozik újra a sikertelen lekérdezésekkel, és nem áll helyre az aktív tranzakciók során előforduló hibákból. Ezekben a forgatókönyvekben használja az alkalmazásszintű újrapróbálkozás logikáját.

Django adatbázis köztes szoftver újrapróbálkozáshoz

Hozzon létre egy Django köztes szoftvert, amely észleli az átmeneti hibákat, és újrapróbálkozza az adatbázis-műveletet. Ez a megközelítés a nézetszintű kérések kezelésére használható:

# 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

A köztes szoftver regisztrálása a következő helyen settings.py:

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

Important

Helyezze az DatabaseRetryMiddleware adatbázist elérő többi köztes szoftver elé, hogy észlelhesse és újrapróbálkozhassa a teljes kérelemfolyamat átmeneti hibáit.

Dekorátor újrapróbálkozása adott műveletekhez

A finomabb szabályozáshoz használjon dekorátort az egyes funkciókhoz:

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

Alkalmazza a dekoratőrt az adatbázis-nehéz függvényekre:

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

Újrapróbálkozás tranzakciókkal

Ha egy tranzakción belül átmeneti hiba történik, a kiszolgáló visszaállítja a teljes tranzakciót. Próbálja újra a teljes tranzakciót, ne csak a sikertelen utasítást:

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

Ne próbálkozzon újra a(z) transaction.atomic() elemen belül. Az újrapróbálkozást indító dekorátornak a teljes atomic() blokkot be kell burkolnia, hogy minden újrapróbálkozás új tranzakciót indukáljon.

Utasításszintű hibák

Az előző szakaszban található hibalista a kapcsolatszintű hibákat ismerteti. Két másik hibát is gyakran újrapróbálnak utasításszinten:

  • 1205: A munkamenet holtpont áldozatául lett kiválasztva. Ismételje meg a tranzakciót.
  • 1222: A zárolási kérelem időtúllépése lejárt. Futtassa újra a tranzakciót, vagy növelje a LOCK_TIMEOUT értékét ennél a munkamenetnél, ha az alapértelmezett érték túl agresszív.

ConnectRetryCount automatikusan újrapróbálkozik megszakadt kapcsolatok esetén, így ez nem vonatkozik ezekre az utasításszintű hibákra. Kezelje ezeket ugyanazzal a dekorátormintával úgy, hogy a biztonságosan újrafuttatható tranzakciókhoz hozzáadja a(z) "1205" és "1222" elemeket a(z) TRANSIENT_ERROR_CODES elemhez.

CONN_MAX_AGE és elavult kapcsolatok

A Django újra felhasználja az adatbázis-kapcsolatokat a kérések között, amikor CONN_MAX_AGE be van állítva. A hosszú élettartamú kapcsolatok elavulttá válhatnak, ha a kiszolgáló bezárja azt (például egy Azure SQL skálázási művelet vagy egy tűzfal időtúllépése során).

Állítsa be az újrahasználat és az elavultság egyensúlyának beállítását 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 (alapértelmezett): Zárja be a kapcsolatot az egyes kérések végén. Legbiztonságosabb, de leglassabb.
  • CONN_MAX_AGE=600: 10 percig újra felhasználhatja a kapcsolatokat. Jó egyensúly a legtöbb webalkalmazás számára.
  • CONN_MAX_AGE=None: A kapcsolatok határozatlan ideig nyitva maradnak. Csak az elavult kapcsolatok újrapróbálkozási mechanizmusával használható.

CONN_HEALTH_CHECKS (Django 4.1 és újabb verziók)

Bevezettük CONN_HEALTH_CHECKSa Django 4.1-et, amely minden kérés előtt ellenőrzi az újrahasznált kapcsolatot. Engedélyezze a(z) CONN_MAX_AGE mellett az elavult kapcsolatok automatikus észlelését:

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

Ha engedélyezve van az állapot-ellenőrzés, a Django egy egyszerű érvényesítési lekérdezést ad ki a kapcsolat újbóli használata előtt. Ha a kapcsolat megszakadt, a Django transzparensen megnyit egy újat ahelyett, hogy hibát jelezne.

Bevált gyakorlatok

  • Használjon exponenciális visszalépést teljes jitterrel. Duplázza meg a felső határt minden kísérletnél, majd várakozzon egy véletlenszerű ideig a [0, cap] intervallumon belül. A véletlenszerű késleltetés megakadályozza, hogy sok ügyfél egy regionális szolgáltatáskimaradás során egyszerre próbálkozzon újra, ami egyébként egy rövid ideig tartó hibát tartós túlterheléssé alakíthat. A kísérletenkénti alvó állapotot (például 30 másodpercet) korlátozza, így a teljes helyreállítási idő korlátozott marad.
  • Állítsa be az újrapróbálkozás felső határát. Három, exponenciális visszalépéssel végzett újrapróbálkozás ésszerű alapértelmezett érték. Több mint öt újrapróbálkozás általában nem átmeneti problémát jelez.
  • Az újrapróbálkozás előtt zárja be a kapcsolatot. Hívja meg connection.close() , hogy Django új kapcsolatot létesítsen a következő kísérlet során.
  • Minden újrapróbálkozás naplózása. A csendesen sikeres újrapróbálkozások elrejthetik a teljesítményproblémákat. Jelentkezzen be a WARNING szinten, hogy nyomon tudja követni a gyakoriságot.
  • Ne próbálja újra a nem átmeneti hibák esetén. A hitelesítési hibák, az engedélyhibák és a szintaxishibák nem hasznosak az újrapróbálkozásokban.
  • Próbálkozzon újra a teljes tranzakcióval. Foglalja a(z) transaction.atomic() elemet az újrapróbálkozási logikába, ne fordítva.
  • Engedélyezi CONN_HEALTH_CHECKS (Django 4.1 és újabb verziók) azokat a webalkalmazásokat, amelyek használják CONN_MAX_AGE.