Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Подключения к SQL Server и Azure SQL могут завершиться временным сбоем по причинам, не имеющим ничего общего с кодом:
- Группа доступности Always On переключается при сбое.
- Сеть удаляет пакет во время настройки подключения.
- Resource Governor ограничивает ресурсы базы данных.
- Во время масштабирования или обновления реплика Azure SQL перезапускается.
Большинство таких сбоев устраняется в течение нескольких секунд. В этой статье показано, как повторно выполнять операции при временных ошибках в приложении Django, использующем серверную часть mssql-django, а также как настроить Django и драйвер ODBC для автоматического восстановления после разрывов соединения из-за простоя.
Временные ошибки
Временные ошибки являются временными сбоями, которые разрешаются самостоятельно. Повторная попытка операции после короткой задержки обычно завершается успешно.
Следующие ошибки являются временными, когда они происходят во время создания подключения или при отправке запроса на сервер. Повторите попытку на коротком, ограниченном обратном выходе. Ошибки, которые сохраняются после нескольких повторных попыток, обычно указывают на проблему конфигурации (неправильный сервер, отсутствующие разрешения, исчерпанную квоту), которая не исправится.
| 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-подключение удаляется в середине подтверждения. Это не ошибка учетных данных. Если он сохраняется, проверьте нестабильность сети на стороне клиента или промежуточное устройство, которое удаляет половину установленных подключений. |
233 |
The client was unable to establish a connection because of an error during connection initialization process before login. |
Сбой транспорта предварительного входа или TLS. Сервер обычно возвращает его, когда он не может принимать подключение (исчерпание ресурсов, достигнуто максимальное число подключений или неподдерживаемый клиент). Это не ошибка учетных данных. Проверьте работоспособность сервера, а затем проверьте время ожидания входа клиента, параметры TLS и совместимость версий TLS клиента и сервера. |
4060 |
Cannot open database "%.*ls" requested by the login. The login failed. |
Вход в систему проходит проверку подлинности, но не удаётся открыть запрошенную базу данных. К временным причинам относятся переходное состояние базы данных (переключение на резервный ресурс, восстановление, масштабирование) или ее автоматическая приостановка. Постоянные причины (база данных не существует, логин не имеет доступа) не будут устранены повторной попыткой; проверьте имя базы данных, сопоставление логина и состояние базы данных. |
4221 |
Login to read-secondary failed due to long wait on 'HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING'. |
Реплика недоступна для входа в систему, так как для транзакций, которые ещё выполнялись в момент пересоздания реплики, отсутствуют версии строк. Откатите или зафиксируйте активные транзакции на основном сервере, чтобы устранить проблему. Снизьте влияние этой проблемы, избегая длительных транзакций записи на основном сервере. |
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.) |
Локальная сторона прерывает подключение. Проверьте работоспособность сети на стороне клиента и любой локальный брандмауэр или 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.) |
Удаленная сторона отправляет сброс TCP. Распространенные причины: сбой однорангового процесса, брандмауэр ввел сброс или шлюз Azure SQL закрыл неактивное подключение. Для сценариев сброса простаивающих соединений включите TCP keepalive на стороне клиента или сократите тайм-аут простоя пула подключений. |
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. |
База данных превышает ограничение Azure SQL управления ресурсами. Идентификатор ресурса 1 указывает на лимит рабочих процессов, а идентификатор ресурса 2 — на лимит сеансов. Определите тип ограничения из сообщения, а затем уменьшите параллелизм, масштабируйте базу данных или сократите длительные операции хранения ресурса. |
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. |
База данных превысила минимально гарантированный уровень, и лежащий в ее основе сервер ограничивает производительность. Повторная попытка обычно бывает успешной, когда нагрузка на соседний узел снижается. Постоянное повторение таких случаев означает, что вам нужен более высокий тарифный план или менее шумная среда. |
40020, 40143, 40166, 40540 |
Сообщение в слоте Error code %d об ошибке 40197 во время переключения при отказе. |
Встроенные в сообщение об аварийном переключении 40197 подкоды, которые в некоторых путях появляются как основной код ошибки. Обработайте их так же, как 40197. |
40197 |
The service has encountered an error processing your request. Please try again. Error code %d. |
Обновление программного обеспечения, сбой оборудования или другое событие переключения при отказе в Azure SQL. Повторное подключение маршрутизирует вас к работоспособной реплике. Встроенный код ошибки определяет тип переключения при отказе. Если ошибка сохраняется, запишите идентификатор трассировки сеанса и обратитесь в службу поддержки. |
40501 |
The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. |
Ограничение производительности ядра Azure SQL. Рекомендуемая минимальная задержка перед повторной попыткой — 10 секунд. Постоянное ограничение пропускной способности указывает на то, что рабочая нагрузка превысила выделенные базе данных ресурсы; увеличьте уровень обслуживания или уменьшите степень параллелизма. |
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'. |
База данных недоступна, как правило, во время переключения при отказе или ненадолго во время операции масштабирования. Повторите попытку с увеличивающейся задержкой; если проблема сохраняется дольше нескольких минут, запишите идентификатор трассировки сеанса и создайте обращение в службу поддержки. |
42108 |
Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. |
Выделенный пул SQL (Synapse) находится в приостановленном состоянии. Повторная попытка будет успешной только после возобновления пула. Возобновите пул явным образом или запланируйте рабочую нагрузку после возобновления работы пула. |
42109 |
The SQL pool is warming up. Please try again. |
Выделенный пул SQL возобновляется. Повторяйте попытки с увеличивающейся задержкой, пока пул не перейдёт в состояние онлайн; прогрев обычно занимает несколько минут. |
49918 |
Cannot process request. Not enough resources to process request. The service is currently busy. Please retry the request later. |
Сервер в настоящее время не может выделить достаточно ресурсов для удовлетворения запроса. Повторите попытку на обратном выходе. Если ошибка сохраняется, увеличьте вычислительные ресурсы базы данных или эластичного пула. |
49919 |
Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". |
Ограничение параллелизма на уровне подписки для операций управления. Уменьшите количество параллельных вызовов создания и обновления или разнесите их по времени. |
49920 |
Cannot process request. Too many operations in progress for subscription "%ld". |
Ограничение параллелизма на уровне подписки для операций в полете. Уменьшите параллелизм или дождитесь завершения выполняющихся операций. |
Ошибки на уровне инструкций не входят в этот список, поскольку они возникают после установления соединения, и после такого сбоя сеанс остаётся пригодным для использования. Наиболее распространёнными ошибками инструкций, допускающих повторное выполнение, являются 1205 (жертва взаимоблокировки) и 1222 (превышение времени ожидания запроса блокировки). Повторите всю транзакцию, а не одну инструкцию сбоя.
Текст сообщения об ошибке взят из временных ошибок подключения к Azure SQL. Отдельные драйверы поддерживают собственные встроенные списки повторных попыток; в этом каталоге описываются ошибки, которые могут быть повторами в SQL Server, База данных SQL Azure, Управляемый экземпляр SQL Azure, базе данных SQL в Microsoft Fabric и выделенных пулах SQL в Azure Synapse Analytics.
Отказоустойчивость неактивного подключения драйвера ODBC
Драйвер Microsoft ODBC для SQL Server обеспечивает встроенную устойчивость простаивающего подключения с помощью ключевых слов строки подключения ConnectRetryCount и ConnectRetryInterval. Эти настройки обрабатывают соединения, разорванные из-за простоя, на уровне драйвера, ещё до того, как начинает выполняться код вашего приложения.
Включить отказоустойчивость неактивного подключения в 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 | По умолчанию | Description |
|---|---|---|
ConnectRetryCount |
1 | Количество попыток автоматического повторного подключения для неактивных подключений. |
ConnectRetryInterval |
10 | Секунды между попытками повторного подключения. |
Note
Функция восстановления простаивающих подключений повторно устанавливает подключения, которые были разорваны в режиме простоя. Он не повторяет неудачные запросы или не восстанавливается после ошибок, возникающих во время активных транзакций. Для этих сценариев используйте логику повторных попыток на уровне приложения.
Промежуточный слой Django для базы данных с поддержкой повторных попыток
Создайте ПО промежуточного слоя Django, которое перехватывает временные ошибки и повторяет операцию базы данных. Этот подход подходит для обработки запросов на уровне представления:
# 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
Зарегистрируйте промежуточное ПО в settings.py:
MIDDLEWARE = [
"myproject.middleware.DatabaseRetryMiddleware",
"django.middleware.security.SecurityMiddleware",
# ... other middleware
]
Important
Разместите DatabaseRetryMiddleware перед другими компонентами промежуточного ПО, которые обращаются к базе данных, чтобы можно было перехватывать временные ошибки и повторно выполнять запросы во всём конвейере обработки запроса.
Декоратор повторных попыток для определенных операций
Для более детального управления используйте декоратор для отдельных функций:
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
Примените декоратор к функциям с большим количеством баз данных:
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
Повторная попытка с помощью транзакций
При возникновении временной ошибки внутри транзакции все транзакции откатываются сервером. Повторите полную транзакцию, а не только неудачную инструкцию:
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(). Декоратор повторных попыток должен охватывать весь блок atomic(), чтобы каждая повторная попытка начиналась с новой транзакции.
Ошибки на уровне инструкций
Список ошибок в предыдущем разделе охватывает сбои на уровне подключения. Две другие ошибки обычно повторно обрабатываются на уровне оператора:
- 1205: Сеанс был выбран в качестве жертвы взаимоблокировки. Повторно запустите транзакцию.
-
1222: превышено время ожидания запроса на блокировку. Повторно выполните транзакцию или увеличьте
LOCK_TIMEOUTдля сеанса, если значение по умолчанию слишком агрессивно.
ConnectRetryCount повторяет сбои подключений, поэтому он не применяется к этим ошибкам на уровне инструкций. Обработайте их с помощью того же шаблона «Декоратор», добавив "1205" и "1222" к TRANSIENT_ERROR_CODES для транзакций, которые можно безопасно выполнять повторно.
CONN_MAX_AGE и устаревшие соединения
Django повторно использует подключения к базе данных между запросами, если задано CONN_MAX_AGE. Длительное подключение может стать устаревшим, если сервер закрывает его (например, во время операции масштабирования Azure SQL или времени ожидания брандмауэра).
Установите 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(по умолчанию): закройте соединение в конце каждого запроса. Самый безопасный, но самый медленный. -
CONN_MAX_AGE=600: повторное использование подключений в течение 10 минут. Хороший баланс для большинства веб-приложений. -
CONN_MAX_AGE=None: не закрывайте подключения на неопределенный срок. Используйте только механизм повторных попыток для устаревших подключений.
CONN_HEALTH_CHECKS (Django 4.1 и более поздние версии)
В Django 4.1 представлен CONN_HEALTH_CHECKS, который проверяет повторно используемое соединение перед каждым запросом. Включите его вместе с 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,
"CONN_HEALTH_CHECKS": True,
},
}
При включенной проверке работоспособности Django выдает упрощенный запрос проверки перед повторной использованием подключения. Если соединение разорвано, Django прозрачно открывает новое соединение вместо того, чтобы выдавать ошибку.
Лучшие практики
- Используйте экспоненциальную задержку между повторными попытками с полным джиттером. При каждой попытке удваивайте предел, затем выжидайте случайное время в пределах
[0, cap]. Джиттер предотвращает синхронные повторные попытки со стороны множества клиентов при региональном сбое, что в противном случае может превратить кратковременный сбой в длительную перегрузку. Ограничение сна для каждой попытки (например, 30 секунд), поэтому общее время восстановления остается ограниченным. - Установите потолок повторных попыток. Три повторные попытки с экспоненциальной задержкой — разумный вариант по умолчанию. Более пяти повторных попыток обычно указывают на то, что проблема не является временной.
- Закройте подключение перед повтором. Вызовите
connection.close(), чтобы Django открыл новое соединение при следующей попытке. - Регистрируют каждую повторную попытку. Повторные попытки, которые проходят незаметно и успешно, могут скрывать проблемы с производительностью. Войдите на
WARNINGуровне, чтобы отслеживать частоту. - Не повторяйте попытку при непереходящих ошибках. Ошибки проверки подлинности, ошибки разрешений и синтаксические ошибки не получают преимущества от повторных попыток.
- Повторите всю транзакцию. Оберните
transaction.atomic()в механизм повторных попыток, а не наоборот. -
Включить
CONN_HEALTH_CHECKS(Django 4.1 и более поздних версий) для веб-приложений, использующихCONN_MAX_AGE.