Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Artikel wird erläutert, wie Transaktionsbehandlungs- und Isolationsebenen für Django-Anwendungen mithilfe des mssql-django Back-Ends mit SQL Server konfiguriert werden.
Standardverhalten
Standardmäßig wird Django im Autocommit-Modus ausgeführt. Jede Datenbankabfrage wird in einer eigenen Transaktion ausgeführt und sofort zugesichert. Sie können dieses Verhalten mithilfe der Einstellung oder der AUTOCOMMIT Transaktionsverwaltungs-API von Django ändern.
AUTOCOMMIT-Einstellung
Setzen Sie AUTOCOMMIT in Ihrer Datenbankkonfiguration auf False, um den Autocommit-Modus zu deaktivieren:
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
Das Deaktivieren von Autocommit bedeutet, dass Sie Transaktionen explizit committen oder zurückrollen müssen. Die meisten Django-Anwendungen lassen autocommit aktiviert und verwenden transaction.atomic() für bestimmte Vorgänge.
Verwenden Sie `transaction.atomic()`
Datenbankvorgänge in transaction.atomic() einschließen, um sicherzustellen, dass sie in einer einzigen Transaktion ausgeführt werden:
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()
Wenn eine Ausnahme innerhalb eines atomic() Blocks auftritt, wird die gesamte Transaktion zurückgesetzt.
Geschachtelte Transaktionen
Django unterstützt geschachtelte atomic() Blöcke mithilfe der Sicherungspunkte des SQL Servers:
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
Transaktionsisolationsstufen
Konfigurieren Sie die Transaktionsisolationsstufe mithilfe der isolation_level Option in der Datenbankkonfiguration:
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",
},
},
}
Unterstützte Isolationsstufen
| Isolationsstufe | Beschreibung |
|---|---|
READ UNCOMMITTED |
Ermöglicht Dirty Reads. Niedrigste Isolation, höchste Parallelität. |
READ COMMITTED |
SQL Server Standardeinstellung. Verhindert schmutzige Lesevorgänge. |
REPEATABLE READ |
Verhindert schmutzige und nicht wiederholbare Lesevorgänge. |
SNAPSHOT |
Verwendet die Zeilenversionsverwaltung für konsistente Lesevorgänge ohne Blockierung. Erfordert, dass die Momentaufnahmeisolation auf Datenbankebene aktiviert ist. |
SERIALIZABLE |
Höchste Isolation. Verhindert Phantomlesevorgänge. |
Aktivieren der SNAPSHOT-Isolation
Um SNAPSHOT Isolation zu verwenden, aktivieren Sie sie zunächst für die Datenbank:
ALTER DATABASE [<your-database>]
SET ALLOW_SNAPSHOT_ISOLATION ON;
Konfigurieren Sie sie dann in 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",
},
},
}
Verwenden Sie den @transaction.atomic-Dekorator
Transaktionen auf gesamte Ansichtsfunktionen anwenden:
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})
Lesen von Daten ohne Blockierung (NOLOCK-Entsprechung)
Eine häufige Anforderung ist es, SQL Server mit dem Hinweis NOLOCK oder der Isolationsstufe READ UNCOMMITTED abzufragen, um Blockierungen bei stark ausgelasteten Tabellen zu vermeiden. Djangos ORM generiert keine Tabellenhinweise, sie haben jedoch zwei Optionen.
Option 1: Festlegen von READ UNCOMMITTED pro Verbindung
Legen Sie die Isolationsstufe für einen dedizierten schreibgeschützten Datenbankalias auf READ UNCOMMITTED fest, damit sie auf alle Abfragen angewendet wird, die über diese Verbindung ausgeführt werden:
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",
},
},
}
Leiten Sie dann Abfragen an den read_uncommitted Alias weiter:
# 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)
Option 2: Verwenden von rohem SQL mit NOLOCK
Verwenden Sie für gezielte Abfragen für bestimmte Tabellen unformatierte SQL mit dem NOLOCK Tabellenhinweis:
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()
Vorsicht
Sowohl READ UNCOMMITTED als auch NOLOCK erlauben Dirty Reads, das heißt, dass Abfragen Daten aus nicht festgeschriebenen Transaktionen zurückgeben können. Verwenden Sie diese Techniken nur für Berichte oder Analyseabfragen, bei denen absolute Konsistenz nicht erforderlich ist.
Option 3: Verwenden Sie stattdessen die SNAPSHOT-Isolation
SNAPSHOT Isolation bietet konsistente Lesevorgänge ohne Blockierung und ohne schmutzige Lesevorgänge. Es ist die empfohlene Alternative zu NOLOCK für die meisten Workloads:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"isolation_level": "SNAPSHOT",
},
},
}
SNAPSHOT erfordert konfiguration auf Datenbankebene. Siehe "SNAPSHOT-Isolation aktivieren".
Zeilenbasierte Sperrung mit select_for_update()
Djangos select_for_update() wird vollständig vom mssql-django Back-End unterstützt. SQL Server implementiert dies mithilfe von Tabellenhinweisen anstelle der von anderen Datenbanken verwendeten FOR UPDATE-Klausel.
Grundlegende Nutzung
from django.db import transaction
with transaction.atomic():
product = Product.objects.select_for_update().get(pk=1)
product.stock -= 1
product.save()
Das Back-End generiert Folgendes: SELECT ... FROM [myapp_product] WITH (ROWLOCK, UPDLOCK) WHERE ...
NOWAIT und SKIP LOCKED
Die Parameter nowait und skip_locked werden beide unterstützt:
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 | SQL Server-Tabellenhinweis |
|---|---|
| Vorgabe | WITH (ROWLOCK, UPDLOCK) |
nowait=True |
WITH (NOWAIT, ROWLOCK, UPDLOCK) |
skip_locked=True |
WITH (ROWLOCK, UPDLOCK, READPAST) |
Note
select_for_update() muss innerhalb eines transaction.atomic() Blocks verwendet werden. Django löst einen Fehler aus, wenn Sie ihn außerhalb einer Transaktion aufrufen.
Unterschiede von PostgreSQL
- Der
ofParameter (select_for_update(of=(...))) wird nicht unterstützt. Das Backend gibtNotSupportedErroraus, wenn Sie es übergeben. - SQL Server verwendet Hinweise auf Tabellenebene (
UPDLOCK) anstelle von Klauseln auf ZeilenebeneFOR UPDATE. Bei hoher Sperrenkonkurrenz kann die Sperreskalierung dazu führen, dass mehr Zeilen oder Seiten gesperrt werden, als Sie beabsichtigt haben. Verwenden Sie dieSNAPSHOTIsolationsstufe, wenn Sie nicht blockierende Lesevorgänge zusammen mit gesperrten Schreibvorgängen benötigen.