Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
As ligações ao SQL Server e ao SQL do Azure podem falhar temporariamente por razões que não têm nada a ver com o seu código:
- Um grupo de disponibilidade Always On faz failover.
- A rede perde um pacote durante a configuração da ligação.
- Resource Governor limita a base de dados.
- Uma réplica do SQL do Azure é recriada durante um redimensionamento ou uma atualização.
A maioria destas falhas desaparece em segundos. Este artigo mostra como repetir tentativas após erros transitórios numa aplicação Django que utiliza o back-end mssql-django, e como configurar o Django e o controlador ODBC para recuperar automaticamente de quedas de ligações inativas.
Erros transitórios
Erros transitórios são falhas temporárias que se resolvem sozinhas. Repetir a operação após uma breve espera normalmente tem êxito.
Os seguintes erros são transitórios quando ocorrem durante o estabelecimento da ligação ou ao enviar um pedido para o servidor. Tente novamente após um curto intervalo de espera limitado. Erros que persistem para além de algumas tentativas geralmente indicam um problema de configuração (servidor errado, permissões em falta, quota esgotada) que a tentativa novamente não resolve.
| Erro | 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 ligação TCP cai a meio do handshake. Não é uma falha de credenciais. Se persistir, verifique se há instabilidade na rede do lado do cliente ou um dispositivo intermédio que interrompa ligações semiestabelecidas. |
233 |
The client was unable to establish a connection because of an error during connection initialization process before login. |
Falha de transporte pré-login ou TLS. O servidor normalmente devolve-a quando não consegue aceitar a ligação (esgotamento de recursos, ligações máximas atingidas ou um cliente não suportado). Não é uma falha de credenciais. Verifica o estado do servidor e depois verifica o timeout de login do cliente, as definições do TLS e a compatibilidade da versão cliente/servidor TLS. |
4060 |
Cannot open database "%.*ls" requested by the login. The login failed. |
O login autentica, mas não consegue abrir a base de dados solicitada. Causas transitórias incluem a base de dados estar em transição (failover, restauração, escalabilidade) ou em pausa automática. Causas persistentes (a base de dados não existe, o login não tem acesso) não serão corrigidas por uma nova tentativa; Verifique o nome da base de dados, o mapeamento de login e o estado da base de dados. |
4221 |
Login to read-secondary failed due to long wait on 'HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING'. |
A réplica não está disponível para início de sessão porque as versões de linha estão em falta para transações que estavam em curso quando a réplica foi reciclada. Reverta ou compromete as transações ativas no principal para resolver o problema. Atenue evitando transações de escrita longas no primário. |
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.) |
O lado local interrompe a ligação. Verifique a saúde da rede do lado do cliente e qualquer firewall local ou cliente 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.) |
O lado remoto envia um reset TCP. Causas comuns: o processo peer crashava, um firewall injetava um reset, ou o gateway SQL do Azure fechava uma ligação inativa. Para casos de reposição da ligação por inatividade, ative o keepalive de TCP no cliente ou reduza o tempo limite de inatividade do conjunto de ligações. |
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. |
A base de dados ultrapassa um limite de governação de recursos do SQL do Azure. O ID de Recurso 1 indica o limite de trabalhadores; O ID do Recurso 2 indica o limite de sessão. Identifique o tipo de limite a partir da mensagem, depois reduza a concorrência, escale a base de dados ou encurta as operações de longa duração que detêm o recurso. |
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. |
A base de dados ultrapassa a sua garantia mínima e o servidor subjacente está a limitar. Retry normalmente tem sucesso quando a carga do vizinho diminui. Ocorrências prolongadas indicam que precisa de um nível de serviço mais elevado ou de um ambiente menos ruidoso. |
40020, 40143, 40166, 40540 |
Reportado na posição Error code %d do erro 40197 durante a comutação pós-falha. |
Subcódigos incorporados numa mensagem de failover 40197 que alguns caminhos apresentam como o número de erro de nível superior. Trata-os da mesma forma que 40197. |
40197 |
The service has encountered an error processing your request. Please try again. Error code %d. |
Uma atualização de software, falha de hardware ou outro evento de failover no SQL do Azure. Ao restabelecer a ligação, será encaminhado para uma réplica em bom estado. O código de erro incorporado identifica o tipo de failover. Se o erro persistir, regista o ID de rastreamento da sessão e contacta o suporte. |
40501 |
The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. |
Limitação do motor do SQL do Azure. O intervalo mínimo recomendado é de 10 segundos. A limitação contínua indica que a carga de trabalho excedeu a alocação de recursos da base de dados; aumente o nível de serviço ou reduza o processamento simultâneo. |
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'. |
A base de dados está indisponível, geralmente a meio do failover ou brevemente durante uma operação de escala. Tente novamente com intervalo progressivo; se o problema persistir durante mais do que alguns minutos, registe o ID de rastreio da sessão e abra um pedido de suporte. |
42108 |
Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. |
O pool dedicado de SQL (Synapse) está em estado de pausa. A nova tentativa só é bem-sucedida depois de o pool ser reativado. Retome explicitamente o pool ou programe a carga de trabalho para ser executada depois de o pool ser retomado. |
42109 |
The SQL pool is warming up. Please try again. |
O pool dedicado de SQL está a recomeçar. Tente novamente um recuo até a piscina estar online; O aquecimento normalmente demora alguns minutos. |
49918 |
Cannot process request. Not enough resources to process request. The service is currently busy. Please retry the request later. |
O servidor não consegue atualmente alocar recursos suficientes para satisfazer o pedido. Tente novamente após um intervalo de espera. Se o erro persistir, amplie a base de dados ou o elastic pool. |
49919 |
Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". |
Limite de concorrência a nível da subscrição para operações de gestão. Reduza as chamadas paralelas de criação/atualização ou escalone-as. |
49920 |
Cannot process request. Too many operations in progress for subscription "%ld". |
Limite de concorrência ao nível de subscrição para operações em voo. Reduzir o paralelismo ou esperar que as operações em voo se esgotem. |
Os erros ao nível da instrução não constam desta lista porque ocorrem depois de a ligação ter sido estabelecida e a falha não inutiliza a sessão. Os erros mais comuns das instruções passíveis de repetição são 1205 (vítima de impasse [deadlock]) e 1222 (tempo limite do pedido de bloqueio). Tente novamente toda a transação em vez do único extrato falhado.
O texto da mensagem de erro provém de erros de ligação transitória do SQL do Azure. Os controladores individuais mantêm as suas próprias listas de repetição integradas; este catálogo descreve os erros passíveis de repetição no SQL Server, no Base de Dados SQL do Azure, no Azure SQL Managed Instance, na Base de Dados SQL no Microsoft Fabric e em pools de SQL dedicados no Azure Synapse Analytics.
Resiliência das ligações inativas do driver ODBC
O Microsoft ODBC Driver for SQL Server fornece resiliência integrada para ligações inativas através das palavras-chave de cadeia de ligação ConnectRetryCount e ConnectRetryInterval. Estas definições tratam das ligações inativas ao nível do driver, antes de o código da aplicação estar envolvido.
Ativar a resiliência de ligação inativa em extra_params:
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 | Predefinição | Description |
|---|---|---|
ConnectRetryCount |
1 | Número de tentativas automáticas de reconexão para ligações inativas. |
ConnectRetryInterval |
10 | Segundos entre tentativas de reconexão. |
Note
A resiliência de ligações inativas reconecta ligações que foram perdidas enquanto estavam inativas. Não tenta novamente consultas falhadas nem recupera erros que ocorrem durante transações ativas. Para esses cenários, utilize um mecanismo de repetição ao nível da aplicação.
Middleware de base de dados do Django para novas tentativas
Crie um middleware Django que detete erros transitórios e tente novamente a operação da base de dados. Esta abordagem funciona para o tratamento de pedidos a nível de visualização:
# 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
Registe o middleware em settings.py:
MIDDLEWARE = [
"myproject.middleware.DatabaseRetryMiddleware",
"django.middleware.security.SecurityMiddleware",
# ... other middleware
]
Importante
Coloque DatabaseRetryMiddleware antes de qualquer outro middleware que aceda à base de dados, para que possa detetar erros transitórios em todo o pipeline de pedidos e repetir a operação.
Decorador de nova tentativa para operações específicas
Para um controlo mais detalhado, use um decorador para funções individuais:
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
Aplique o decorador a funções com muita base de dados:
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
Tentar novamente com transações
Quando ocorre um erro transitório dentro de uma transação, toda a transação é revertida pelo servidor. Tente novamente a transação completa, não apenas a declaração falhada:
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
Não voltes a tentar lá dentro transaction.atomic(). O decorador de repetição deve envolver todo o bloco atomic() para que cada nova tentativa inicie uma nova transação.
Erros ao nível da declaração
A lista de erros na secção anterior abrange falhas ao nível da ligação. Dois outros erros são geralmente repetidos ao nível da instrução:
- 1205: A sessão foi escolhida como vítima do interbloqueio. Execute novamente a transação.
-
1222: Foi ultrapassado o tempo limite do pedido de bloqueio. Reexecute a transação, ou aumente
LOCK_TIMEOUTpara a sessão se o valor padrão for demasiado agressivo.
ConnectRetryCount tenta novamente ligações interrompidas, por isso não se aplica a estes erros ao nível das instruções. Trate-as com o mesmo padrão decorador, adicionando "1205" e "1222" a TRANSIENT_ERROR_CODES para transações que podem ser reexecutadas em segurança.
CONN_MAX_AGE e conexões obsoletas
O Django reutiliza ligações à base de dados entre pedidos quando CONN_MAX_AGE está definido. Uma ligação de longa duração pode tornar-se obsoleta se o servidor a fechar (por exemplo, durante uma operação de escala SQL do Azure ou um timeout de firewall).
Definir CONN_MAX_AGE para equilibrar a reutilização com a monotonia:
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(predefinição): Fechar a conexão no final de cada pedido. O mais seguro, mas o mais lento. -
CONN_MAX_AGE=600: Reutilizar as ligações durante 10 minutos. Bom equilíbrio para a maioria das aplicações web. -
CONN_MAX_AGE=None: Mantém as ligações abertas indefinidamente. Use apenas com um mecanismo de retentativa para ligações obsoletas.
CONN_HEALTH_CHECKS (Django 4.1 e posteriores)
O Django 4.1 introduziu CONN_HEALTH_CHECKS, que valida uma ligação reutilizada antes de cada pedido. Ative-o juntamente com CONN_MAX_AGE para detetar automaticamente ligações obsoletas:
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,
},
}
Com as verificações de saúde ativadas, o Django emite uma consulta leve de validação antes de reutilizar uma ligação. Se a ligação for quebrada, o Django abre uma nova de forma transparente em vez de gerar um erro.
Melhores práticas
- Use recuo exponencial com jitter completo. Duplique o limite máximo em cada tentativa e, em seguida, aguarde um período aleatório dentro de
[0, cap]. O jitter impede muitos clientes de tentarem novamente em ritmo sincronizado durante uma falha regional, o que pode transformar uma falha breve em sobrecarga sustentada. Limite o sono por tentativa (por exemplo, 30 segundos) para que o tempo total de recuperação fique limitado. - Defina um limite máximo de tentativas. Três tentativas com retrocesso exponencial são um valor predefinido razoável. Mais de cinco tentativas normalmente indicam um problema não transitório.
- Fecha a ligação antes de tentar novamente. Liga
connection.close()para que Django abra uma nova ligação na próxima tentativa. - Regista cada nova tentativa. Novas tentativas que são bem-sucedidas sem aviso podem esconder problemas de desempenho. Regista no nível
WARNINGpara poder acompanhar a frequência. - Não volte a tentar erros não transitórios. Falhas de autenticação, erros de permissão e erros de sintaxe não beneficiam de novas tentativas.
- Tente novamente toda a transação. Coloca
transaction.atomic()dentro da lógica de nova tentativa, e não o contrário. -
Permitir
CONN_HEALTH_CHECKS(Django 4.1 e posteriores) para aplicações web que utilizamCONN_MAX_AGE.