Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek obsahuje pokyny k optimalizaci výkonu aplikace Django při použití back-endu mssql-django s SQL Server.
Optimalizace připojení
Snižte režii připojení laděním nastavení sdružování, trvalosti a časového limitu.
Povolení sdružování připojení
Sdružování připojení je ve výchozím nastavení povolené. Ověřte, že ve vašem počítači settings.pynení zakázaná:
# Keep this True (or omit it entirely) for best connection performance
DATABASE_CONNECTION_POOLING = True
Použijte CONN_MAX_AGE
Nastavte CONN_MAX_AGE , aby byla připojení k databázi otevřená napříč požadavky a vyhnula se režijním nákladům na vytvoření nového připojení pro jednotlivé požadavky:
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",
},
},
}
Nastavení časového limitu dotazu
Zabránit dlouho běžícím dotazům v neomezeném spotřebovávání prostředků:
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,
},
},
}
Optimalizace dotazů
Omezte počet komunikací s databází a dotazů pomocí těchto technik ORM.
Vyhněte se vzorům dotazů N+1
Používá se select_related pro relace cizích klíčů (jeden dotaz JOIN) a prefetch_related pro relace M:N nebo zpětné relace (samostatný dotaz s klauzulí IN):
# 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)
Použijte only() a defer()
Omezte sloupce načtené v případě, že nepotřebujete všechna pole:
# Retrieve only specific fields
products = Product.objects.only("name", "price").all()
# Defer loading of large fields
products = Product.objects.defer("description", "metadata").all()
Použijte values() a values_list()
Pokud nepotřebujete instance modelu, použijte values() nebo values_list() pro méně náročné dotazy:
# Returns dictionaries instead of model instances
prices = Product.objects.values("name", "price")
# Returns tuples
names = Product.objects.values_list("name", flat=True)
Práce v limitu 2 100 parametrů
SQL Server omezí každý dotaz na 2 100 parametrů. Django generuje parametrizované dotazy, takže operace, které vytvářejí velké IN klauzule nebo seznamy hromadných hodnot, mohou dosáhnout tohoto limitu.
Automatická optimalizace velkých klauzulí IN:
Když má volání filter(field__in=list) více než 2 048 hodnot, backend mssql-django automaticky vloží hodnoty do dočasné tabulky (v dávkách po 1 000) a přeformuluje dotaz na WHERE field IN (SELECT params FROM #Temp_params). Tato optimalizace zabraňuje omezení parametrů bez jakýchkoli změn kódu. Platí pro všechna __in vyhledávání, včetně těch, které generuje prefetch_related(). Prahová hodnota 2 048 je nastavená back-endem max_in_list_size() tak, aby byla bezpečně pod limitem 2 100 parametrů SQL Server.
Toto přepsání má svou cenu: vytvoření a naplnění #Temp_params znamená další komunikaci tam a zpět a vyšší zátěž databáze tempdb. U seznamů blížících se prahové hodnoty proveďte srovnávací test obou přístupů ve vaší úloze.
V případě potřeby ručního zásahu:
Automatická optimalizace temp-table pracuje s vyhledáváními __in, ale tyto operace mohou stále narazit na limit 2 100 parametrů, protože každá hodnota pole je samostatným parametrem:
-
bulk_create()nebobulk_update()s mnoha objekty a mnoha poli - Komplexní
Q()výrazy s mnoha zřetězenými podmínkami - Případy, kdy se chcete vyhnout cyklům potřebným k naplnění
#Temp_params(například když je menší seznam a normálníIN (...)by byl rychlejší)
Řešení:
Použít
batch_sizepři hromadných operacích, aby každá dávka byla pod limitem:# 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)Velké
INdotazy rozdělte na části, pokud chcete obejít automatický mechanismus dočasných tabulek: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)Místo materializace seznamů ID použijte poddotazy:
# 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))Použití
Prefetchs filtrovanými sadami dotazů k omezení počtu ID předaných doprefetch_related():from django.db.models import Prefetch orders = Order.objects.prefetch_related( Prefetch("items", queryset=OrderItem.objects.select_related("product")) )[:500] # Limit parent queryset size
Hromadné operace
Používejte hromadné operace ke snížení počtu komunikací s databází:
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
Při použití bulk_create nebo bulk_update nastavte batch_size na základě počtu polí v jednom objektu. Backend bulk_batch_size() omezuje každou dávku na 1 000 řádků a používá konzervativní 2050 / (fields * 2) limit parametrů pro obabulk_create i bulk_update. Dodatečné / 2 je vyhrazeno pro dva parametry pro každé pole, které bulk_update používá (jeden pro porovnání CASE, jeden pro hodnotu), a stejný dělitel se použije i na bulk_create, takže stejná cesta kódu je bezpečná pro obě operace.
Pokud vynecháte batch_size, back-end automaticky vypočítá bezpečnou hodnotu. Můžete také zadat batch_size a back-end ho dále omezí na bezpečný limit.
Další informace o parametrech return_rows_bulk_insert a default parametrech naleznete v tématu Hromadné operace s mssql-django.
Strategie indexu
Django vytvoří indexy automaticky pro ForeignKey, OneToOneFielda pole s db_index=True. Pro další indexy použijte 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"]),
]
Pro SQL Server indexy (například indexy se INCLUDE sloupci) použijte v migracích nezpracovaný SQL:
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;",
),
]
Backend mssql-django podporuje krycí indexy (supports_covering_indexes = True v mssql/features.py). Ve všech verzích Django podporovaných v mssql-django (3.2 a novějších) můžete místo surového SQL použít parametr include u models.Index:
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"),
]
Umístění skupiny souborů
Back-end mssql-django mapuje Django db_tablespace na klauzuli SQL ServerON filegroup. Slouží k umístění tabulek nebo indexů do konkrétních skupin souborů:
class LargeAuditLog(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
message = models.TextField()
class Meta:
db_tablespace = "ARCHIVE_FG"
Tím se vygeneruje: CREATE TABLE ... ON [ARCHIVE_FG].
Important
Skupina souborů již musí existovat v databázi SQL Server před spuštěním migrate. Vytvořte jej pomocí ALTER DATABASE [<your-database>] ADD FILEGROUP [ARCHIVE_FG] a přidejte do něj alespoň jeden soubor.
Funkce okna
Back-end podporuje funkce oken SQL Server (supports_over_clause = True). Výrazy Django Window slouží k hodnocení, průběžným součtům a děleným výpočtům:
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 nepodporuje NTH_VALUE(). Místo toho použijte FIRST_VALUE, LAST_VALUE nebo alternativní řešení s poddotazem. Viz Omezení a nepodporované funkce v mssql-django.
Monitorování výkonu dotazů
K identifikaci pomalých dotazů během vývoje použijte integrované protokolování dotazů Django:
LOGGING = {
"version": 1,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"loggers": {
"django.db.backends": {
"level": "DEBUG",
"handlers": ["console"],
},
},
}
Pro pracovní a produkční úlohy použijte nástroje pro výkon SQL Server k analýze SQL, který Django generuje:
Začněte s integrovanými sestavami výkonu před přímým dotazem na zobrazení dynamické správy.
- Pro SQL Server a Azure SQL Managed Instance použijte řídicí panel výkonu v SQL Server Management Studio.
- Pro Azure SQL Database použijte nástroj Query Performance Insight pro Azure SQL Database.
Tyto reporty jsou obvykle nejrychlejším způsobem, jak najít náročné dotazy, čekání, blokování a zatížení prostředků, a přitom ponechávají menší prostor pro chyby než ad hoc dotazy DMV.
Pomocí Query Store identifikujte dotazy s nejvyšší spotřebou prostředků a dotazy, u kterých nedávno došlo k regresi.
Pomocí zobrazení Dotazy s nejvyšší spotřebou prostředků, Dotazy se zhoršeným výkonem a Statistiky čekání dotazů v SQL Server Management Studio určete, zda je úzkým hrdlem procesor, vstupně-výstupní operace, paměť nebo čekání. Pokyny najdete v tématu Osvědčené postupy pro monitorování úloh pomocí Query Store.
Otevřete skutečný plán spuštění pro pomalý příkaz a vyhledejte skenování, nákladná vyhledání klíčů, nepřesné odhady počtu řádků a chybějící indexy.
Pokud se dotaz po změně nasazení nebo schématu zpomalí, porovnejte plány v Query Store před změnou kódu aplikace. DBA může dočasně vynutit ověřený funkční plán, zatímco opravujete základní problém v indexu, statistikách nebo struktuře dotazu.
Pokud Query Store místo vysokého využití procesoru zobrazuje čekání, použijte Identifikovat úzká místa k rozlišení problémů souvisejících s procesorem, pamětí, diskovými vstupně-výstupními operacemi, přetížením připojení a blokováním.
Související obsah
- Hromadné operace s mssql-django
- Sdružování připojení v mssql-django
- Logika opakování a odolnost připojení pomocí mssql-django
- Referenční informace ke konfiguraci mssql-django
- Řídicí panel výkonu
- Query Performance Insight pro službu Azure SQL Database
- Sledujte výkon pomocí nástroje Query Store
- Analýza skutečného plánu provádění
- Optimalizace databáze Django