Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Koneksi ke SQL Server dan Azure SQL dapat gagal sementara karena alasan yang tidak ada hubungannya dengan kode Anda:
- Grup ketersediaan AlwaysOn gagal.
- Jaringan kehilangan paket saat penyiapan koneksi.
- Resource Governor membatasi laju database.
- Replika Azure SQL didaur ulang selama skala atau peningkatan.
Sebagian besar kegagalan ini jelas dalam hitungan detik. Artikel ini menjelaskan cara melakukan percobaan ulang saat terjadi kesalahan sementara dalam aplikasi Django yang menggunakan backend mssql-django, serta cara mengonfigurasi Django dan driver ODBC agar dapat pulih secara otomatis dari terputusnya koneksi idle.
Kesalahan sementara
Kesalahan sementara adalah kegagalan sementara yang diselesaikan sendiri. Mencoba kembali operasi setelah penundaan singkat biasanya berhasil.
Kesalahan berikut bersifat sementara ketika terjadi selama pembentukan koneksi atau saat mengirim permintaan ke server. Coba lagi setelah penundaan singkat yang dibatasi. Kesalahan yang bertahan melewati beberapa percobaan ulang biasanya menunjukkan masalah konfigurasi (server yang salah, izin yang hilang, kuota habis) yang coba lagi tidak akan diperbaiki.
| Kesalahan | 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.) |
Koneksi TCP terputus di tengah proses handshake. Bukan kegagalan kredensial. Jika berlanjut, periksa ketidakstabilan jaringan sisi klien atau perangkat perantara yang menghilangkan koneksi yang setengah dibuat. |
233 |
The client was unable to establish a connection because of an error during connection initialization process before login. |
Transportasi pra-login atau kegagalan TLS. Server biasanya mengembalikannya ketika tidak dapat menerima koneksi (kelelahan sumber daya, koneksi maks tercapai, atau klien yang tidak didukung). Bukan kegagalan kredensial. Verifikasi kesehatan server, lalu periksa batas waktu masuk klien, pengaturan TLS, dan kompatibilitas versi TLS klien/server. |
4060 |
Cannot open database "%.*ls" requested by the login. The login failed. |
Login mengautentikasi tetapi tidak dapat membuka database yang diminta. Penyebab sementara antara lain database sedang dalam transisi (failover, pemulihan, penskalaan) atau dijeda secara otomatis. Penyebab yang bersifat permanen (database tidak ada, login tidak memiliki akses) tidak akan teratasi dengan mencoba lagi; periksa nama database, pemetaan login, dan status database. |
4221 |
Login to read-secondary failed due to long wait on 'HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING'. |
Replika tidak tersedia untuk masuk karena versi baris hilang untuk transaksi yang sedang dalam penerbangan saat replika didaur ulang. Batalkan atau komitkan transaksi yang aktif di server primer untuk menyelesaikan masalah. Kurangi risikonya dengan menghindari transaksi penulisan yang berlangsung lama pada node utama. |
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.) |
Pihak lokal memutuskan koneksi. Periksa kesehatan jaringan sisi klien dan firewall lokal atau klien VPN apa pun. |
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.) |
Sisi jarak jauh mengirimkan reset TCP. Penyebab umum: proses peer mengalami kegagalan, firewall mengirim reset, atau gateway Azure SQL menutup koneksi yang tidak aktif. Untuk pola reset saat idle, aktifkan TCP keepalive pada klien atau perpendek waktu tunggu idle pada pool koneksi. |
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. |
Database melebihi batas tata kelola sumber daya Azure SQL. ID Sumber Daya 1 menunjukkan batas pekerja; ID Sumber Daya 2 menunjukkan batas sesi. Identifikasi jenis batas berdasarkan pesan, lalu kurangi tingkat konkurensi, tingkatkan kapasitas database, atau persingkat operasi berjalan lama yang menggunakan sumber daya tersebut terlalu lama. |
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. |
Database telah melampaui batas jaminan minimumnya, dan server yang mendasarinya sedang mengalami pembatasan. Percobaan ulang biasanya berhasil saat beban pada node tetangga menurun. Kejadian berkelanjutan menunjukkan Bahwa Anda memerlukan tingkat layanan yang lebih tinggi atau lingkungan yang kurang bising. |
40020,40143,40166,40540 |
Dilaporkan pada slot Error code %d pada kesalahan 40197 selama failover. |
Sub-kode yang disematkan dalam pesan failover 40197 bahwa beberapa jalur muncul sebagai nomor kesalahan tingkat atas. Perlakukan mereka sama seperti 40197. |
40197 |
The service has encountered an error processing your request. Please try again. Error code %d. |
Peningkatan perangkat lunak, kegagalan perangkat keras, atau peristiwa failover lainnya di Azure SQL. Penyambungan ulang akan mengarahkan Anda ke replika yang sehat. Kode kesalahan yang disematkan mengidentifikasi jenis failover. Jika kesalahan berlanjut, ambil ID pelacakan sesi dan hubungi dukungan. |
40501 |
The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. |
Pelambatan mesin Azure SQL. Batas minimum yang direkomendasikan adalah backoff 10 detik. Throttling yang berkelanjutan menunjukkan bahwa beban kerja telah melebihi alokasi sumber daya database; tingkatkan tingkat layanan atau kurangi tingkat konkurensi. |
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'. |
Database tidak tersedia, biasanya saat failover sedang berlangsung atau sebentar selama operasi penskalaan. Coba lagi pada backoff; jika bertahan beberapa menit, ambil ID pelacakan sesi dan buka kasus dukungan. |
42108 |
Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. |
Kumpulan SQL terdedikasi (Synapse) berada dalam status dijeda. Percobaan ulang berhasil hanya setelah pool dilanjutkan kembali. Aktifkan kembali pool secara eksplisit, atau jadwalkan beban kerja agar berjalan setelah pool diaktifkan kembali. |
42109 |
The SQL pool is warming up. Please try again. |
Pool SQL khusus sedang dilanjutkan. Coba kembali dengan jeda bertahap hingga pool aktif; inisialisasi awal biasanya memerlukan beberapa menit. |
49918 |
Cannot process request. Not enough resources to process request. The service is currently busy. Please retry the request later. |
Server saat ini tidak dapat mengalokasikan sumber daya yang cukup untuk memenuhi permintaan. Coba lagi pada backoff. Jika kesalahan berlanjut, tingkatkan database atau kumpulan elastis. |
49919 |
Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". |
Batas konkurensi pada tingkat langganan untuk operasi manajemen. Kurangi panggilan pembuatan/pembaruan secara paralel atau beri jeda di antaranya. |
49920 |
Cannot process request. Too many operations in progress for subscription "%ld". |
Batas konkurensi tingkat langganan pada operasi dalam penerbangan. Kurangi paralelisme atau tunggu hingga operasi dalam penerbangan terkuras. |
Kesalahan pada tingkat pernyataan tidak ada dalam daftar ini karena kesalahan tersebut terjadi setelah koneksi terjalin dan kegagalan tersebut membuat sesi tetap dapat digunakan. Kesalahan pernyataan yang dapat dicoba ulang yang paling umum adalah 1205 (korban deadlock) dan 1222 (batas waktu permintaan penguncian). Ulangi seluruh transaksi alih-alih satu pernyataan yang gagal.
Teks pesan kesalahan berasal dari kesalahan koneksi sementara Azure SQL. Driver individual mempertahankan daftar coba lagi bawaan mereka sendiri; katalog ini menjelaskan kesalahan mana yang memenuhi syarat untuk mencoba kembali di SQL Server, Azure SQL Database, Azure SQL Managed Instance, database SQL di Microsoft Fabric, dan kumpulan SQL khusus di Azure Synapse Analytics.
Ketahanan koneksi tidak aktif pada driver ODBC
Driver ODBC Microsoft untuk SQL Server menyediakan ketahanan bawaan terhadap koneksi idle melalui kata kunci string koneksi ConnectRetryCount dan ConnectRetryInterval. Pengaturan ini mengatasi koneksi idle yang terputus di tingkat driver, sebelum kode aplikasi Anda terlibat.
Aktifkan ketahanan koneksi menganggur di 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 | Default | Deskripsi |
|---|---|---|
ConnectRetryCount |
1 | Jumlah upaya koneksi ulang otomatis untuk koneksi yang menganggur. |
ConnectRetryInterval |
10 | Detik antara upaya koneksi ulang. |
Note
Ketahanan koneksi saat tidak aktif menyambung ulang koneksi yang terputus saat tidak aktif. Ini tidak mencoba ulang kueri yang gagal atau melakukan pemulihan dari kesalahan yang terjadi selama transaksi aktif. Untuk skenario tersebut, gunakan logika coba lagi tingkat aplikasi.
Middleware Django untuk percobaan ulang basis data
Buat middleware Django yang menangkap kesalahan sementara dan mencoba kembali operasi database. Pendekatan ini berfungsi untuk penanganan permintaan tingkat tampilan:
# 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
Daftarkan middleware di settings.py:
MIDDLEWARE = [
"myproject.middleware.DatabaseRetryMiddleware",
"django.middleware.security.SecurityMiddleware",
# ... other middleware
]
Important
Tempatkan DatabaseRetryMiddleware sebelum middleware lain yang mengakses database sehingga dapat menangkap dan mencoba kembali kesalahan sementara dari seluruh alur permintaan.
Coba lagi dekorator untuk operasi tertentu
Untuk kontrol yang lebih halus, gunakan dekorator pada fungsi individual:
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
Terapkan dekorator pada fungsi yang banyak mengakses database:
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
Coba lagi transaksi
Ketika kesalahan sementara terjadi di dalam transaksi, seluruh transaksi digulung balik oleh server. Coba lagi transaksi lengkap, bukan hanya pernyataan yang gagal:
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()
Perhatian
Jangan coba lagi di dalam transaction.atomic(). Dekorator retry harus membungkus seluruh blok atomic() sehingga setiap percobaan ulang dimulai dengan transaksi baru.
Kesalahan tingkat pernyataan
Daftar kesalahan di bagian sebelumnya mencakup kegagalan tingkat koneksi. Dua kesalahan lainnya biasanya dicoba kembali pada tingkat pernyataan:
- 1205: Sesi dipilih sebagai korban kebuntuan. Jalankan kembali transaksi.
-
1222: Batas waktu permintaan penguncian terlampaui. Jalankan ulang transaksi, atau tingkatkan
LOCK_TIMEOUTuntuk sesi jika nilai default terlalu agresif.
ConnectRetryCount mencoba kembali koneksi yang rusak, sehingga tidak berlaku untuk kesalahan tingkat pernyataan ini. Tangani semuanya menggunakan pola dekorator yang sama dengan menambahkan "1205" dan "1222" ke TRANSIENT_ERROR_CODES untuk transaksi yang aman dijalankan kembali.
CONN_MAX_AGE dan koneksi kedaluwarsa
Django menggunakan kembali koneksi basis data antarpermintaan saat CONN_MAX_AGE diatur. Koneksi yang berlangsung lama mungkin menjadi kedaluwarsa jika server menutup koneksi tersebut (misalnya, selama operasi penskalaan Azure SQL atau batas waktu firewall).
Atur CONN_MAX_AGE untuk menyeimbangkan penggunaan kembali terhadap keusangan:
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(default): Tutup koneksi di akhir setiap permintaan. Paling aman tapi paling lambat. -
CONN_MAX_AGE=600: Gunakan kembali koneksi selama 10 menit. Keseimbangan yang baik untuk sebagian besar aplikasi web. -
CONN_MAX_AGE=None: Menjaga koneksi tetap terbuka tanpa batas waktu. Gunakan hanya dengan mekanisme percobaan ulang untuk koneksi usang.
CONN_HEALTH_CHECKS (Django 4.1 dan yang lebih baru)
Django 4.1 memperkenalkan CONN_HEALTH_CHECKS, yang memvalidasi koneksi yang digunakan kembali sebelum setiap permintaan. Aktifkan bersama CONN_MAX_AGE untuk mendeteksi koneksi kedaluarsa secara otomatis:
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,
},
}
Dengan pemeriksaan kesehatan diaktifkan, Django menjalankan kueri validasi ringan sebelum menggunakan kembali koneksi. Jika koneksi terputus, Django secara transparan membuka koneksi baru alih-alih memunculkan error.
Praktik terbaik
- Gunakan backoff eksponensial dengan jitter penuh. Gandakan batas pada setiap upaya, lalu tunggu selama durasi acak dalam
[0, cap]. Jitter mencegah banyak klien mencoba lagi secara serempak selama gangguan regional, yang jika tidak dapat membuat gangguan singkat berkembang menjadi beban berlebih yang berkepanjangan. Batasi waktu tunggu per percobaan (misalnya, 30 detik) agar total waktu pemulihan tetap terbatas. - Atur batas maksimum percobaan ulang. Tiga percobaan ulang dengan backoff eksponensial adalah default yang wajar. Lebih dari lima kali percobaan ulang biasanya menunjukkan adanya masalah yang tidak bersifat sementara.
- Tutup koneksi sebelum mencoba kembali. Panggil
connection.close()agar Django membuka koneksi baru pada upaya berikutnya. - Catat setiap coba lagi. Percobaan ulang yang berhasil tanpa terlihat dapat menyembunyikan masalah kinerja. Catat log pada tingkat
WARNINGagar Anda dapat melacak frekuensi. - Jangan coba lagi jika terjadi kesalahan yang tidak bersifat sementara. Kegagalan autentikasi, kesalahan izin, dan kesalahan sintaksis tidak mendapat manfaat dari percobaan ulang.
- Coba lagi seluruh transaksi. Bungkus
transaction.atomic()di dalam logika coba lagi, bukan sebaliknya. -
Mengaktifkan
CONN_HEALTH_CHECKS(Django 4.1 dan yang lebih baru) untuk aplikasi web yang menggunakanCONN_MAX_AGE.