Tranzakciókezelés az mssql-django-ban

Ez a cikk bemutatja, hogyan konfigurálhatja a tranzakciókezelési és elkülönítési szinteket a Django-alkalmazásokhoz a háttérrendszer és a mssql-django SQL Server használatával.

Alapértelmezett viselkedés

Alapértelmezés szerint a Django autocommit módban működik. Minden adatbázis-lekérdezés a saját tranzakciójában fut, és azonnal véglegesítése történik. Ezt a viselkedést a beállítással vagy a AUTOCOMMIT Django tranzakciókezelési API-jával módosíthatja.

AUTOCOMMIT beállítás

Állítsa a(z) AUTOCOMMIT értékét False értékre az adatbázis-konfigurációban az autocommit mód kikapcsolásához:

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

Az automatikus kiküldés letiltása azt jelenti, hogy kifejezetten véglegesítenie vagy vissza kell állítania a tranzakciókat. A legtöbb Django-alkalmazás engedélyezi az automatikus kommunikációt, és adott műveletekhez használja transaction.atomic() .

A transaction.atomic() használata

Foglalja az adatbázisműveleteket a(z) transaction.atomic() elembe, hogy azok egyetlen tranzakción belül hajtódjanak végre:

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()

Ha kivétel történik egy atomic() blokkon belül, a rendszer visszaállítja a teljes tranzakciót.

Beágyazott tranzakciók

A Django az SQL Server mentési pontjai révén támogatja a beágyazott atomic() blokkokat:

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

Tranzakciós izolációs szintek

Állítsa be a tranzakciók elkülönítési szintjét az adatbázis konfigurációjában található isolation_level beállítással:

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",
        },
    },
}

Támogatott elkülönítési szintek

Elkülönítési szint Description
READ UNCOMMITTED Engedélyezi a piszkos olvasást. Legalacsonyabb elkülönítés, legmagasabb egyidejűség.
READ COMMITTED SQL Server alapértelmezett érték. Megakadályozza a piszkos olvasásokat.
REPEATABLE READ Megakadályozza a piszkos és nem ismételhető olvasásokat.
SNAPSHOT A konzisztens olvasás érdekében sorverziókezelést használ blokkolás nélkül. Az adatbázisszintű pillanatkép-elkülönítés engedélyezését igényli.
SERIALIZABLE A legnagyobb elszigeteltség. Megakadályozza a fantomolvasást.

A SNAPSHOT elkülönítésének engedélyezése

Az elkülönítés használatához SNAPSHOT először engedélyezze az adatbázisban:

ALTER DATABASE [<your-database>]
SET ALLOW_SNAPSHOT_ISOLATION ON;

Ezután konfigurálja itt: 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",
        },
    },
}

Használja a(z) @transaction.atomic dekorátort

Tranzakciók alkalmazása teljes nézetfüggvényekre:

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})

Adatok olvasása blokkolás nélkül (NOLOCK-ekvivalens)

Gyakori kérés, hogy a SQL Servert a NOLOCK hinttel vagy READ UNCOMMITTED izolációs szinttel kérdezzék le, hogy elkerüljék a blokkolást a nagy terhelésű táblák esetén. A Django ORM-je nem hoz létre táblázatos tippeket, de két lehetősége van.

1. lehetőség: A READ UNCOMMITTED beállítása kapcsolatonként

Állítsa be az elkülönítési szintet a dedikált, csak olvasható adatbázisálneven READ UNCOMMITTED értékre, hogy az az adott kapcsolaton futó összes lekérdezésre érvényes legyen:

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",
        },
    },
}

Ezután a lekérdezések átirányítása az read_uncommitted aliashoz:

# 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)

2. lehetőség: Nyers SQL használata a NOLOCK használatával

Adott táblák célzott lekérdezéseihez használja a nyers SQL-t a NOLOCK táblamutatóval:

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()

Caution

A(z) READ UNCOMMITTED és a(z) NOLOCK is lehetővé teszi a piszkos olvasást, ami azt jelenti, hogy a lekérdezések nem véglegesített tranzakciókból származó adatokat is visszaadhatnak. Ezeket a technikákat csak olyan jelentéskészítési vagy elemzési lekérdezésekhez használja, ahol nincs szükség abszolút konzisztenciára.

3. lehetőség: helyette használjon SNAPSHOT izolációt

SNAPSHOT az elkülönítés konzisztens olvasást biztosít blokkolás és piszkos olvasások nélkül. A legtöbb számítási feladat esetében ez az ajánlott alternatíva a NOLOCK helyett:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>",
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "isolation_level": "SNAPSHOT",
        },
    },
}

SNAPSHOT adatbázisszintű konfigurációt igényel. Lásd A SNAPSHOT-izoláció engedélyezése.

Sorszintű zárolás a select_for_update() használatával

A Django-t select_for_update() teljes mértékben támogatja a mssql-django háttérrendszer. SQL Server ezt a más adatbázisok által használt záradék helyett FOR UPDATE táblázatos tippek használatával valósítja meg.

Alapszintű használat

from django.db import transaction

with transaction.atomic():
    product = Product.objects.select_for_update().get(pk=1)
    product.stock -= 1
    product.save()

A háttérrendszer a következőt hozza létre: SELECT ... FROM [myapp_product] WITH (ROWLOCK, UPDLOCK) WHERE ...

NOWAIT és SKIP LOCKED

A nowait paraméterek és skip_locked a paraméterek egyaránt támogatottak:

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]
Paraméter SQL Server-táblamutató
Alapértelmezett WITH (ROWLOCK, UPDLOCK)
nowait=True WITH (NOWAIT, ROWLOCK, UPDLOCK)
skip_locked=True WITH (ROWLOCK, UPDLOCK, READPAST)

Note

A(z) select_for_update() elemet egy transaction.atomic() blokkon belül kell használni. A Django hibát jelez, ha egy tranzakción kívül hívja meg.

Különbségek a PostgreSQL-től

  • A of paraméter (select_for_update(of=(...))) nem támogatott. A háttérrendszer NotSupportedError hibát dob, ha átadja.
  • SQL Server sorszintű UPDLOCK záradékok helyett táblázatszintű tippeket (FOR UPDATE) használ. Nagy versengés esetén a zárolás eszkalációja több sort vagy oldalt zárolhat, mint a cél. A SNAPSHOT elkülönítési szintet akkor használja, ha blokkolásmentes olvasásokra van szüksége a zárolt írási műveletek mellett.