mssql-django'da işlem yönetimi

Bu makalede, SQL Server ile arka ucu kullanarak mssql-django Django uygulamaları için işlem işleme ve yalıtım düzeylerini yapılandırma işlemi açıklanmaktadır.

Varsayılan davranış

Varsayılan olarak, Django otomatik komut modunda çalışır. Her veritabanı sorgusu ayrı bir işlem içinde çalışır ve hemen onaylanır. Ayarı veya Django'nun işlem yönetimi API'sini AUTOCOMMIT kullanarak bu davranışı değiştirebilirsiniz.

AUTOCOMMIT ayarı

Otomatik onay modunu devre dışı bırakmak için, veritabanı yapılandırmanızda AUTOCOMMIT değerini False olarak ayarlayın:

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

Otomatik komutu devre dışı bırakmak, işlemleri açıkça işlemeniz veya geri almanız gerektiği anlamına gelir. Çoğu Django uygulaması otomatik işlemeyi etkin bırakır ve belirli işlemler için transaction.atomic() kullanır.

transaction.atomic() kullanma

Veritabanı işlemlerini, tek bir işlem içinde yürütülmelerini sağlamak için transaction.atomic() içine alın:

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

atomic() bloğu içinde herhangi bir istisna oluşursa, işlemin tamamı geri alınır.

İç içe geçmiş işlemler

Django, SQL Server'ın kaydetme noktaları aracılığıyla iç içe atomic() bloklarını destekler:

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

İşlem yalıtım düzeyleri

Veritabanı yapılandırmanızdaki seçeneği kullanarak isolation_level işlem yalıtım düzeyini yapılandırın:

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

Desteklenen yalıtım düzeyleri

Yalıtım düzeyi Açıklama
READ UNCOMMITTED Kirli okumalara izin verir. En düşük yalıtım, en yüksek eşzamanlılık.
READ COMMITTED Varsayılan SQL Server. Kirli okumaları önler.
REPEATABLE READ Kirli ve tekrarlanamayan okumaları önler.
SNAPSHOT Engellemeden tutarlı okumalar için satır sürümleme kullanır. Veritabanı düzeyinde anlık görüntü yalıtımının etkinleştirilmesini gerektirir.
SERIALIZABLE En yüksek yalıtım. Hayalet okumaları engeller.

SNAPSHOT yalıtımını etkinleştir

SNAPSHOT yalıtımını kullanmak için önce bunu veritabanında etkinleştirin:

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

Ardından bunu settings.py içinde yapılandırın:

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

@transaction.atomic dekoratörü kullanın

Tüm görünüm işlevlerine işlem uygulama:

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

Engelleme olmadan verileri okuma (NOLOCK eşdeğeri)

Yaygın isteklerden biri, yoğun tablolarda bloklamayı önlemek için SQL Server’ı NOLOCK ipucuyla veya READ UNCOMMITTED yalıtım düzeyiyle sorgulamaktır. Django'nun ORM'sı tablo ipuçları oluşturmaz, ancak iki seçeneğiniz vardır.

Seçenek 1: Bağlantı başına READ UNCOMMITTED ayarlama

Bu bağlantıdaki tüm sorgulara uygulanması için, yalıtım düzeyini salt okunur özel bir veritabanı takma adında READ UNCOMMITTED olarak ayarlayın:

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

Ardından sorguları diğer adla yönlendirin read_uncommitted :

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

Seçenek 2: NOLOCK ile ham SQL kullanma

Belirli tablolardaki hedeflenen sorgular için ham SQL'i NOLOCK tablo ipucuyla birlikte kullanın:

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

READ UNCOMMITTED Hem hem de NOLOCK kirli okumalara izin verir, bu da sorguların kaydedilmemiş işlemlerden veri döndürebileceği anlamına gelir. Bu teknikleri yalnızca mutlak tutarlılığın gerekli olmadığı raporlama veya analiz sorguları için kullanın.

3. Seçenek: Bunun yerine SNAPSHOT yalıtımını kullanın

SNAPSHOT yalıtım, engelleme olmadan ve kirli okumalar olmadan tutarlı okumalar sağlar. Çoğu iş yükü için NOLOCK yerine önerilen alternatiftir:

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

SNAPSHOT veritabanı düzeyinde yapılandırma gerektirir. Bkz. SNAPSHOT yalıtımını etkinleştirme.

select_for_update() ile satır düzeyi kilitleme

Django’nun select_for_update() özelliği, mssql-django arka ucu tarafından tam olarak desteklenmektedir. SQL Server bunu diğer veritabanları tarafından kullanılan yan tümce yerine FOR UPDATE tablo ipuçlarını kullanarak uygular.

Temel kullanım

from django.db import transaction

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

Arka uç aşağıdakileri oluşturur: SELECT ... FROM [myapp_product] WITH (ROWLOCK, UPDLOCK) WHERE ...

NOWAIT ve SKIP LOCKED

nowait ve skip_locked parametrelerinin her ikisi de desteklenir:

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]
Parametre SQL Server tablo ipucu
Varsayılan WITH (ROWLOCK, UPDLOCK)
nowait=True WITH (NOWAIT, ROWLOCK, UPDLOCK)
skip_locked=True WITH (ROWLOCK, UPDLOCK, READPAST)

Note

select_for_update() bir transaction.atomic() bloğun içinde kullanılmalıdır. Django, işlemi bir işlemin dışında çağırırsanız bir hata oluşturur.

PostgreSQL'den farklar

  • of parametresi (select_for_update(of=(...))) desteklenmez. Bunu iletirseniz, arka uç NotSupportedError fırlatır.
  • SQL Server satır düzeyi yan tümceleri yerine tablo düzeyinde UPDLOCK ipuçları (FOR UPDATE) kullanır. Yoğun çekişme durumunda, kilit yükseltmesi hedeflediğinizden daha fazla satırın veya sayfanın kilitlenmesine neden olabilir. Kilitli yazma işlemleriyle birlikte engellenmeyen okumalara ihtiyacınız varsa SNAPSHOT yalıtım düzeyini kullanın.