Az mssql-django teljesítményhangolása

Ez a cikk útmutatást nyújt a Django-alkalmazások teljesítményének optimalizálásához a mssql-django háttérrendszer SQL Server való használatakor.

Kapcsolatoptimalizálás

Csökkentse a kapcsolat többletterhelését a készletezési, adatmegőrzési és időtúllépési beállítások finomhangolásával.

Kapcsolatkészletezés engedélyezése

A kapcsolatkészletezés alapértelmezés szerint engedélyezve van. Ellenőrizze, hogy nincs-e letiltva a(z) settings.py beállításaiban:

# Keep this True (or omit it entirely) for best connection performance
DATABASE_CONNECTION_POOLING = True

CONN_MAX_AGE használata

Állítsa be CONN_MAX_AGE , hogy az adatbázis-kapcsolatok nyitva maradjanak a kérések között, elkerülve az új kapcsolatok létrehozásának többletterhelését az egyes kérésekhez:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "USER": "<your-username>",
        "PASSWORD": "<your-password>",
        "HOST": "<your-server>",
        "PORT": "1433",
        "CONN_MAX_AGE": 600,  # Keep connections open for 10 minutes
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
    },
}

Lekérdezési időkorlát beállítása

Megakadályozza, hogy a hosszan futó lekérdezések határozatlan ideig használják fel az erőforrásokat:

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",
            "query_timeout": 30,
        },
    },
}

Lekérdezésoptimalizálás

Ezekkel az ORM-technikákkal csökkentheti az adatbázis-körutakat és a lekérdezések számát.

N+1 lekérdezési minták elkerülése

Idegenkulcs-kapcsolatok esetén használja a select_related elemet (egyetlen JOIN lekérdezéshez), a több-többhöz vagy fordított kapcsolatok esetén pedig a prefetch_related elemet (külön lekérdezés IN záradékkal):

# Bad: N+1 queries
orders = Order.objects.all()
for order in orders:
    print(order.customer.name)  # Each access triggers a query

# Good: Single JOIN query
orders = Order.objects.select_related("customer").all()
for order in orders:
    print(order.customer.name)  # No additional queries

# Good: Two queries instead of N+1
orders = Order.objects.prefetch_related("items").all()
for order in orders:
    for item in order.items.all():  # Uses prefetched data
        print(item.name)

Az only() és a defer() használata

Korlátozza a lekért oszlopokat, ha nincs szüksége az összes mezőre:

# Retrieve only specific fields
products = Product.objects.only("name", "price").all()

# Defer loading of large fields
products = Product.objects.defer("description", "metadata").all()

Értékek() és values_list() használata

Ha nincs szüksége modellpéldányokra, könnyebb lekérdezésekhez használja a values() vagy values_list() elemet:

# Returns dictionaries instead of model instances
prices = Product.objects.values("name", "price")

# Returns tuples
names = Product.objects.values_list("name", flat=True)

Munka a 2100 paraméterkorláton belül

SQL Server az egyes lekérdezéseket 2100 paraméterre korlátozza. A Django paraméteres lekérdezéseket hoz létre, így a nagy IN záradékokat vagy tömeges értéklistákat létrehozó műveletek elérik ezt a korlátot.

Nagy IN záradékok automatikus optimalizálása:

Ha egy filter(field__in=list) hívás több mint 2048 értéket tartalmaz, a mssql-django háttérrendszer automatikusan beszúrja az értékeket egy ideiglenes táblába (1000-es kötegekben), és újraírja a lekérdezést WHERE field IN (SELECT params FROM #Temp_params). Ez az optimalizálás kódmódosítás nélkül elkerüli a paraméterkorlátot. Ez az összes __in lekérdezésre vonatkozik, beleértve a prefetch_related() által generáltakat is. A 2048-at a háttérrendszer úgy állítja max_in_list_size() be, hogy biztonságosan maradjon SQL Server 2100 paraméterkorlátja alatt.

Ennek az átírásnak ára van: a #Temp_params létrehozása és feltöltése további hálózati fordulókat és tempdb-tevékenységet eredményez. A küszöbértékhez közeli listák esetében mérje fel a számítási feladat mindkét megközelítését.

Ha még szükség van manuális beavatkozásra:

Az automatikus temp-table optimalizálás kezeli a(z) __in kereséseket, de ezek a műveletek továbbra is beleütközhetnek a 2 100 paraméteres korlátba, mert minden mezőérték külön paraméter:

  • bulk_create() vagy bulk_update() sok objektummal és számos mezővel
  • Összetett Q() kifejezések számos láncolt feltétellel
  • Olyan esetek, amikor el szeretné kerülni a feltöltéshez #Temp_params szükséges ciklikus utakat (például ha egy kisebb lista és egy normál IN (...) gyorsabb lenne)

Megoldások:

  1. Használja a(z) batch_size elemet tömeges műveleteknél, hogy minden köteg a korláton belül maradjon:

    # Backend cap with 10 fields: min(1000, 2050 // 10 // 2) = 102 rows per batch
    # The backend applies the conservative // 2 divisor for both bulk_create and bulk_update.
    Product.objects.bulk_create(products, batch_size=100)
    
  2. Nagy adattömb-lekérdezésekIN, ha meg szeretné kerülni az automatikus temp-table mechanizmust:

    from itertools import islice
    
    def chunked_filter(queryset, field, values, chunk_size=2000):
        """Filter a queryset in chunks to stay within the 2,100 parameter limit."""
        results = []
        it = iter(values)
        while chunk := list(islice(it, chunk_size)):
            results.extend(queryset.filter(**{f"{field}__in": chunk}))
        return results
    
    # Returns a list of model instances, not a QuerySet
    products = chunked_filter(Product.objects, "pk", large_id_list)
    
  3. Használjon allekérdezéseket az azonosítólisták materializálása helyett:

    # Instead of: Order.objects.filter(product_id__in=list(Product.objects.values_list("id", flat=True)))
    # Use a subquery (Django generates a single SQL statement with no parameter explosion)
    Order.objects.filter(product__in=Product.objects.filter(active=True))
    
  4. Használja a Prefetch elemet szűrt lekérdezéshalmazokkal a prefetch_related() számára átadott azonosítók számának korlátozásához:

    from django.db.models import Prefetch
    
    orders = Order.objects.prefetch_related(
        Prefetch("items", queryset=OrderItem.objects.select_related("product"))
    )[:500]  # Limit parent queryset size
    

Tömeges műveletek

Tömeges műveletek használata az adatbázis-körutak számának csökkentéséhez:

from decimal import Decimal

from myapp.models import Product

# Bulk create
new_products = [Product(name=f"Item {i}", price=Decimal("1.99") * i) for i in range(1000)]
Product.objects.bulk_create(new_products, batch_size=500)

# Bulk update: refetch so each instance has a primary key
products = list(Product.objects.filter(name__startswith="Item "))
for product in products:
    product.price *= Decimal("1.10")
Product.objects.bulk_update(products, ["price"], batch_size=500)

Important

A(z) bulk_create vagy bulk_update használatakor állítsa be a(z) batch_size értékét az objektumonkénti mezők száma alapján. A háttérrendszer bulk_batch_size() az egyes kötegek méretét 1 000 sorban maximalizálja, és konzervatív 2050 / (fields * 2) paraméterkorlátot alkalmaz mindkettőrebulk_create és bulk_update. A további / 2 a bulk_update által mezőnként használt két paraméter számára van fenntartva (egy a CASE-illesztéshez, egy az értékhez), és ugyanez az osztó vonatkozik a bulk_create elemre is, így ugyanaz a kódútvonal mindkét művelethez biztonságosan használható.

Ha kihagyja batch_size, a háttérrendszer automatikusan kiszámít egy biztonságos értéket. Megadhat egy batch_size értéket is, és a háttérrendszer ezt tovább korlátozza a biztonságos határértékre.

A return_rows_bulk_insert és default paraméterekkel kapcsolatos további információkért lásd: Tömeges műveletek az mssql-django használatával.

Indexelési stratégiák

A Django automatikusan létrehozza az indexeket a ForeignKey, OneToOneFieldés a mezőkhöz.db_index=True További indexek esetén használja a következőt Meta.indexes:

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100, db_index=True)
    category = models.CharField(max_length=50)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        indexes = [
            models.Index(fields=["category", "price"]),
            models.Index(fields=["-created_at"]),
        ]

Az SQL Server-specifikus indexek (például oszlopokat tartalmazó INCLUDE indexek) esetében használja a nyers SQL-t a migrálásokban:

from django.db import migrations

class Migration(migrations.Migration):
    dependencies = [("myapp", "0001_initial")]
    operations = [
        migrations.RunSQL(
            sql="CREATE INDEX IX_product_category ON myapp_product (category) INCLUDE (name, price);",
            reverse_sql="DROP INDEX IX_product_category ON myapp_product;",
        ),
    ]

A mssql-django háttérprogram támogatja a lefedő indexeket (supports_covering_indexes = True a következőben: mssql/features.py). A (3.2 és újabb verziók) által mssql-django támogatott összes Django-verzión használhatja a paramétert include a models.Index nyers SQL helyett:

class Product(models.Model):
    name = models.CharField(max_length=100)
    category = models.CharField(max_length=50)
    price = models.DecimalField(max_digits=10, decimal_places=2)

    class Meta:
        indexes = [
            models.Index(fields=["category"], include=["name", "price"], name="ix_product_cat_cover"),
        ]

Fájlcsoport elhelyezése

A mssql-django backend a Django db_tablespace elemét az SQL Server ON filegroup záradékának felelteti meg. Ezzel táblákat vagy indexeket helyezhet el adott fájlcsoportokon:

class LargeAuditLog(models.Model):
    timestamp = models.DateTimeField(auto_now_add=True)
    message = models.TextField()

    class Meta:
        db_tablespace = "ARCHIVE_FG"

Ez a következőt hozza létre: CREATE TABLE ... ON [ARCHIVE_FG].

Important

A fájlcsoportnak már léteznie kell a SQL Server adatbázisban a futtatás migrateelőtt. Hozza létre a(z) ALTER DATABASE [<your-database>] ADD FILEGROUP [ARCHIVE_FG] használatával, és adjon hozzá legalább egy fájlt.

Ablakfunkciók

A háttérrendszer támogatja SQL Server ablakfüggvényeit (supports_over_clause = True). Django-kifejezések Window használata rangsoroláshoz, összegek futtatásához és particionált számításokhoz:

from django.db.models import F, Window
from django.db.models.functions import Rank, RowNumber

# Rank products by price within each category
products = Product.objects.annotate(
    price_rank=Window(
        expression=Rank(),
        partition_by=F("category"),
        order_by=F("price").desc(),
    )
)

# Row numbers across the full result set
products = Product.objects.annotate(
    row_num=Window(
        expression=RowNumber(),
        order_by=F("created_at").asc(),
    )
)

Note

SQL Server nem támogatjaNTH_VALUE(). Használja inkább a FIRST_VALUE, a LAST_VALUE vagy egy allekérdezéses kerülő megoldást. Lásd az mssql-django korlátozásait és nem támogatott funkcióit.

Lekérdezési teljesítmény figyelése

Használja a Django beépített lekérdezésnaplózását a lassú lekérdezések azonosításához a fejlesztés során:

LOGGING = {
    "version": 1,
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
    },
    "loggers": {
        "django.db.backends": {
            "level": "DEBUG",
            "handlers": ["console"],
        },
    },
}

Előkészítési és éles számítási feladatokhoz használja SQL Server teljesítményeszközöket a Django által létrehozott SQL elemzéséhez:

  1. A DMV-k közvetlen lekérdezése előtt kezdje a beépített teljesítményjelentésekkel.

    Ezek a jelentések általában a leggyorsabb módja annak, hogy drága lekérdezéseket, várakozásokat, blokkolást és erőforrás-terhelést találjanak, kevesebb hibalehetőséggel, mint az alkalmi DMV-lekérdezések.

  2. A Query Store segítségével azonosíthatja a legtöbb erőforrást használó lekérdezéseket, valamint azokat a lekérdezéseket, amelyek teljesítménye nemrég romlott.

  3. A SQL Server Management Studio leggyakoribb erőforrás-használó lekérdezései, regressziós lekérdezései és lekérdezési várakozási statisztikai nézetei alapján állapítsa meg, hogy a szűk keresztmetszet a PROCESSZOR, az I/O, a memória vagy a várakozás. Útmutatásért tekintse meg a számítási feladatok Query Store való monitorozásának ajánlott eljárásait.

  4. Nyissa meg a lassú utasításhoz tartozó tényleges végrehajtási tervet, és keressen benne beolvasásokat, költséges kulcskereséseket, pontatlan sorbecsléseket és hiányzó indexeket.

  5. Ha egy lekérdezés lassabb lett az üzembe helyezés vagy a séma módosítása után, hasonlítsa össze a terveit Query Store az alkalmazáskód módosítása előtt. Az adatbázis-adminisztrátor ideiglenesen kikényszeríthet egy ismerten jó végrehajtási tervet, amíg elhárítják az indexszel, a statisztikákkal vagy a lekérdezés felépítésével kapcsolatos problémát.

Ha a Query Store a magas CPU-idő helyett várakozásokat mutat, használja a Szűk keresztmetszetek azonosítása funkciót a processzor-, memória-, lemez-I/O-, kapcsolatterhelési és blokkolási problémák elkülönítésére.