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.
Artikel ini menjelaskan cara mengonfigurasi penanganan transaksi dan tingkat isolasi untuk aplikasi Django menggunakan mssql-django backend dengan SQL Server.
Perilaku bawaan
Secara default, Django beroperasi dalam mode autocommit. Setiap kueri basis data berjalan dalam transaksinya masing-masing dan langsung dikomit. Anda dapat mengubah perilaku ini menggunakan AUTOCOMMIT pengaturan atau API manajemen transaksi Django.
Pengaturan AUTOCOMMIT
Atur AUTOCOMMIT ke False dalam konfigurasi database Anda untuk menonaktifkan mode autocommit:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"USER": "<your-username>",
"PASSWORD": "<your-password>",
"HOST": "<your-server>",
"PORT": "1433",
"AUTOCOMMIT": False,
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
},
},
}
Note
Menonaktifkan autocommit berarti Anda harus secara eksplisit menerapkan atau membatalkan transaksi. Sebagian besar aplikasi Django membiarkan autocommit diaktifkan dan digunakan transaction.atomic() untuk operasi tertentu.
Gunakan transaction.atomic()
Bungkus operasi database dalam transaction.atomic() agar dieksekusi dalam satu transaksi:
from django.db import transaction
from myapp.models import Account
def transfer_funds(from_account_id, to_account_id, amount):
with transaction.atomic():
sender = Account.objects.select_for_update().get(pk=from_account_id)
receiver = Account.objects.select_for_update().get(pk=to_account_id)
sender.balance -= amount
receiver.balance += amount
sender.save()
receiver.save()
Jika ada pengecualian yang terjadi di dalam atomic() blok, seluruh transaksi akan digulung balik.
Transaksi berlapis
Django mendukung blok berlapis atomic() melalui titik penyimpanan SQL Server:
from django.db import transaction
with transaction.atomic():
# Outer transaction
Product.objects.create(name="Widget A", price=9.99)
try:
with transaction.atomic():
# Inner savepoint
Product.objects.create(name="Widget B", price=14.99)
raise ValueError("Simulated error")
except ValueError:
pass # Inner savepoint is rolled back, outer continues
# Widget A is committed, Widget B is not
Tingkat isolasi transaksi
Konfigurasikan tingkat isolasi transaksi menggunakan isolation_level opsi dalam konfigurasi database Anda:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"USER": "<your-username>",
"PASSWORD": "<your-password>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"isolation_level": "READ COMMITTED",
},
},
}
Tingkat isolasi yang didukung
| Tingkat isolasi | Description |
|---|---|
READ UNCOMMITTED |
Memungkinkan bacaan kotor. Isolasi terendah, konkurensi tertinggi. |
READ COMMITTED |
Bawaan SQL Server. Mencegah pembacaan kotor. |
REPEATABLE READ |
Mencegah pembacaan kotor dan tidak dapat diulang. |
SNAPSHOT |
Menggunakan pembuatan versi baris untuk pembacaan konsisten tanpa pemblokiran. Memerlukan isolasi rekam jepret tingkat database untuk diaktifkan. |
SERIALIZABLE |
Isolasi tertinggi. Mencegah pembacaan hantu. |
Mengaktifkan isolasi SNAPSHOT
Untuk menggunakan SNAPSHOT isolasi, aktifkan pada database terlebih dahulu:
ALTER DATABASE [<your-database>]
SET ALLOW_SNAPSHOT_ISOLATION ON;
Kemudian konfigurasikan di settings.py:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"USER": "<your-username>",
"PASSWORD": "<your-password>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"isolation_level": "SNAPSHOT",
},
},
}
Gunakan dekorator @transaction.atomic
Terapkan transaksi ke seluruh fungsi tampilan:
from django.db import transaction
from django.http import JsonResponse
@transaction.atomic
def create_order(request):
# All database operations in this view run in a single transaction
order = Order.objects.create(customer_id=request.user.id)
for item in request.POST.getlist("items"):
OrderItem.objects.create(order=order, product_id=item)
return JsonResponse({"order_id": order.pk})
Membaca data tanpa pemblokiran (setara NOLOCK)
Permintaan umum adalah mengkueri SQL Server dengan NOLOCK petunjuk atau READ UNCOMMITTED isolasi untuk menghindari pemblokiran pada tabel yang sibuk. ORM Django tidak menghasilkan petunjuk tabel, tetapi Anda memiliki dua opsi.
Opsi 1: Tetapkan READ UNCOMMITTED untuk setiap koneksi
Atur tingkat isolasi ke READ UNCOMMITTED pada alias database baca-saja khusus untuk menerapkannya ke semua kueri pada koneksi tersebut:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
},
},
"read_uncommitted": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"isolation_level": "READ UNCOMMITTED",
},
},
}
Kemudian rutekan kueri ke read_uncommitted alias:
# Read with NOLOCK-equivalent behavior
products = Product.objects.using("read_uncommitted").filter(active=True)
# Writes still go through the default connection
Product.objects.create(name="Widget", price=9.99)
Opsi 2: Gunakan SQL mentah dengan NOLOCK
Untuk kueri yang ditargetkan pada tabel tertentu, gunakan SQL mentah dengan NOLOCK petunjuk tabel:
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT id, name, price FROM myapp_product WITH (NOLOCK) WHERE active = %s", [1])
rows = cursor.fetchall()
Perhatian
Baik READ UNCOMMITTED maupun NOLOCK mengizinkan dirty read, yang berarti kueri dapat mengembalikan data dari transaksi yang belum dikomitkan. Gunakan teknik ini hanya untuk kueri pelaporan atau analitik di mana konsistensi absolut tidak diperlukan.
Opsi 3: Gunakan isolasi SNAPSHOT sebagai gantinya
SNAPSHOT isolasi menyediakan bacaan yang konsisten tanpa memblokir dan tanpa bacaan kotor. Ini adalah alternatif yang direkomendasikan untuk NOLOCK bagi sebagian besar beban kerja:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"isolation_level": "SNAPSHOT",
},
},
}
SNAPSHOT memerlukan konfigurasi pada tingkat basis data. Lihat Mengaktifkan isolasi SNAPSHOT.
Penguncian tingkat baris dengan select_for_update()
select_for_update() Django didukung sepenuhnya oleh backend mssql-django. SQL Server menerapkan ini menggunakan petunjuk tabel alih-alih klausul FOR UPDATE yang digunakan oleh basis data lain.
Penggunaan dasar
from django.db import transaction
with transaction.atomic():
product = Product.objects.select_for_update().get(pk=1)
product.stock -= 1
product.save()
Backend menghasilkan: SELECT ... FROM [myapp_product] WITH (ROWLOCK, UPDLOCK) WHERE ...
NOWAIT dan SKIP TERKUNCI
Parameter nowait dan skip_locked keduanya didukung:
from django.db import transaction
# Raise DatabaseError immediately if the row is already locked
with transaction.atomic():
product = Product.objects.select_for_update(nowait=True).get(pk=1)
# Skip rows that are locked by other transactions
with transaction.atomic():
available = Product.objects.select_for_update(skip_locked=True).filter(
reserved=False
)[:10]
| Parameter | Petunjuk tabel SQL Server |
|---|---|
| Default | WITH (ROWLOCK, UPDLOCK) |
nowait=True |
WITH (NOWAIT, ROWLOCK, UPDLOCK) |
skip_locked=True |
WITH (ROWLOCK, UPDLOCK, READPAST) |
Note
select_for_update() harus digunakan di dalam blok transaction.atomic(). Django menghasilkan error jika Anda memanggilnya di luar transaksi.
Perbedaan dari PostgreSQL
- Parameter
of(select_for_update(of=(...))) tidak didukung. Backend memunculkanNotSupportedErrorjika Anda meneruskannya. - SQL Server menggunakan petunjuk tingkat tabel (
UPDLOCK) alih-alih klausa tingkatFOR UPDATEbaris. Dalam kondisi persaingan akses yang tinggi, eskalasi kunci dapat menyebabkan lebih banyak baris atau halaman terkunci daripada yang Anda maksudkan.SNAPSHOTGunakan tingkat isolasi jika Anda memerlukan bacaan non-pemblokiran bersama tulisan terkunci.