이 문서에서는 SQL Server 백 엔드를 사용할 mssql-django 때 Django 애플리케이션 성능을 최적화하는 방법에 대한 지침을 제공합니다.
연결 최적화
풀링, 지속성 및 시간 제한 설정을 조정하여 연결 오버헤드를 줄입니다.
연결 풀링 활성화
연결 풀링이 기본적으로 사용하도록 설정됩니다.
settings.py에서 비활성화되지 않았는지 확인하세요:
# Keep this True (or omit it entirely) for best connection performance
DATABASE_CONNECTION_POOLING = True
CONN_MAX_AGE 사용
요청 간에 데이터베이스 연결을 열어 두도록 설정 CONN_MAX_AGE 하여 각 요청에 대해 새 연결을 설정하는 오버헤드를 방지합니다.
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",
},
},
}
쿼리 시간 제한 설정
장기 실행 쿼리가 리소스를 무기한 사용하지 않도록 방지합니다.
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,
},
},
}
쿼리 최적화
이러한 ORM 기술을 사용하여 데이터베이스 왕복 및 쿼리 수를 줄입니다.
N+1 쿼리 패턴 방지
외래 키 관계(단일 JOIN 쿼리)에는 select_related를 사용하고, 다대다 또는 역방향 관계(IN 절이 있는 별도의 쿼리)에는 prefetch_related를 사용합니다:
# 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)
only() 및 defer() 사용
모든 필드가 필요하지 않은 경우 검색되는 열을 제한합니다.
# Retrieve only specific fields
products = Product.objects.only("name", "price").all()
# Defer loading of large fields
products = Product.objects.defer("description", "metadata").all()
값() 및 values_list() 사용
모델 인스턴스가 필요하지 않은 경우 더 가벼운 쿼리를 사용 values() 하거나 values_list() 사용합니다.
# Returns dictionaries instead of model instances
prices = Product.objects.values("name", "price")
# Returns tuples
names = Product.objects.values_list("name", flat=True)
2,100개 매개 변수 제한 내에서 작업
SQL Server 각 쿼리를 2,100개의 매개 변수로 제한합니다. Django는 매개 변수가 있는 쿼리를 생성하므로 큰 IN 절 또는 대량 값 목록을 생성하는 작업은 이 제한에 도달할 수 있습니다.
큰 IN 절에 대한 자동 최적화:
filter(field__in=list) 호출에 2,048개 이상의 값 mssql-django 이 있는 경우 백 엔드는 자동으로 임시 테이블(1,000개 일괄 처리)에 값을 삽입하고 쿼리를 다시 WHERE field IN (SELECT params FROM #Temp_params)작성합니다. 이 최적화는 코드를 변경하지 않고 매개 변수 제한을 방지합니다.
prefetch_related()에 의해 생성된 조회를 포함한 모든 __in 조회에 적용됩니다. 2,048 임계값은 백 엔드가 max_in_list_size() SQL Server 2,100 매개 변수 제한에 안전하게 유지되도록 설정됩니다.
이 재작성에는 비용이 있습니다. #Temp_params를 만들고 채우면 추가적인 왕복 통신과 tempdb 활동이 늘어납니다. 임계값에 가까운 목록의 경우 워크로드의 두 접근 방식을 모두 벤치마킹합니다.
수동 개입이 여전히 필요한 경우:
자동 임시 테이블 최적화는 조회를 __in 처리하지만 각 필드 값이 별도의 매개 변수이므로 이러한 작업은 여전히 2,100개의 매개 변수 제한에 도달할 수 있습니다.
-
bulk_create()또는bulk_update()여러 개체 및 여러 필드가 있는 경우 - 연결된 조건이 많은 복합
Q()식 -
#Temp_params을 채우는 데 필요한 왕복 호출을 피하고 싶은 경우(예: 더 작은 목록과 일반적인IN (...)을 사용하는 편이 더 빠른 경우)
해결 방법:
대량 작업에
batch_size을 사용하여 각 배치가 제한을 넘지 않도록 하세요:# 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)자동 임시 테이블 메커니즘을 우회하려는 경우 큰
IN쿼리를 청크로 나누세요: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)ID 목록을 구체화하는 대신 하위 쿼리를 사용합니다.
# 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))Prefetch를 필터링된 쿼리셋과 함께 사용하여prefetch_related()에 전달되는 ID의 수를 제한합니다.from django.db.models import Prefetch orders = Order.objects.prefetch_related( Prefetch("items", queryset=OrderItem.objects.select_related("product")) )[:500] # Limit parent queryset size
대량 작업
대량 작업을 사용하여 데이터베이스 왕복 횟수를 줄입니다.
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
bulk_create 또는 bulk_update를 사용하는 경우, 개체당 필드 수에 따라 batch_size를 설정합니다. 백 엔드의 bulk_batch_size() 각 일괄 처리는 1,000개 행으로 제한되며 두 행 2050 / (fields * 2)bulk_create에 보수적인 bulk_update 매개 변수 제한을 적용합니다. 추가 / 2 항목은 사용하는 필드 bulk_update 당 두 개의 매개 변수(CASE 일치에 대해 하나, 값에 대해 하나)에 대해 예약되어 있으며 동일한 수수가 적용 bulk_create 되므로 동일한 코드 경로가 두 작업 모두에 안전합니다.
생략 batch_size하면 백 엔드가 안전 값을 자동으로 계산합니다. 또한 batch_size를 지정할 수도 있으며, 백엔드가 이를 안전한 한도로 추가 제한합니다.
return_rows_bulk_insert 및 default 매개 변수에 대한 자세한 내용은 mssql-django를 사용한 대량 작업을 참조하세요.
인덱스 전략
Django는 ForeignKey, OneToOneField 및 db_index=True가 있는 필드에 대해 자동으로 인덱스를 생성합니다. 추가 인덱스의 경우 다음을 사용합니다 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"]),
]
SQL Server 관련 인덱스(예: 열이 있는 INCLUDE 인덱스)의 경우 마이그레이션에서 원시 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;",
),
]
mssql-django 백엔드는 커버링 인덱스(supports_covering_indexes = True in mssql/features.py)를 지원합니다.
mssql-django에서 지원하는 모든 Django 버전(3.2 이상)에서는 원시 SQL 대신 models.Index에서 include 매개변수를 사용할 수 있습니다.
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"),
]
파일 그룹 배치
mssql-django 백엔드는 Django의 db_tablespace를 SQL Server의 ON filegroup 절에 매핑합니다. 이를 사용하여 특정 파일 그룹에 테이블 또는 인덱스를 배치합니다.
class LargeAuditLog(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
message = models.TextField()
class Meta:
db_tablespace = "ARCHIVE_FG"
다음을 생성합니다: CREATE TABLE ... ON [ARCHIVE_FG]
Important
실행하기 전에 migrate파일 그룹이 SQL Server 데이터베이스에 이미 있어야 합니다.
ALTER DATABASE [<your-database>] ADD FILEGROUP [ARCHIVE_FG]로 이를 만들고 하나 이상의 파일을 추가합니다.
창 함수
백 엔드는 SQL Server 창 함수(supports_over_clause = True)를 지원합니다. 순위, 실행 합계 및 분할된 계산에 Django의 Window 식을 사용합니다.
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는 NTH_VALUE()을 지원하지 않습니다. 대신 , FIRST_VALUE또는 하위 쿼리 해결 방법을 사용합니다LAST_VALUE.
mssql-django에서 제한 사항 및 지원되지 않는 기능을 참조하세요.
쿼리 성능 모니터링
Django의 기본 제공 쿼리 로깅을 사용하여 개발 중에 느린 쿼리를 식별합니다.
LOGGING = {
"version": 1,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"loggers": {
"django.db.backends": {
"level": "DEBUG",
"handlers": ["console"],
},
},
}
스테이징 및 프로덕션 워크로드의 경우 SQL Server 성능 도구를 사용하여 Django에서 생성하는 SQL을 분석합니다.
DMV를 직접 쿼리하기 전에 기본 제공 성능 보고서로 시작합니다.
- SQL Server 및 Azure SQL Managed Instance 경우 SQL Server Management Studio 성능 대시보드를 사용합니다.
- Azure SQL Database 경우 Azure SQL Database Query Performance Insight를 사용합니다.
이러한 보고서는 일반적으로 임시 DMV 쿼리보다 실수의 여지가 적은 비용이 드는 쿼리, 대기, 차단 및 리소스 압력을 찾는 가장 빠른 방법입니다.
쿼리 저장소 사용하여 최근 회귀된 상위 리소스 소비 쿼리 및 쿼리를 식별합니다.
SQL Server Management Studio 리소스 사용 상위 쿼리, 회귀 쿼리 및 쿼리 대기 통계 보기를 사용하여 병목 상태가 CPU, I/O, 메모리 또는 대기인지 확인합니다. 지침은 쿼리 저장소 사용하여 워크로드를 모니터링하는 모범 사례를 참조하세요.
느리게 실행되는 문에 대한 실제 실행 계획을 열고 스캔, 비용이 많이 드는 키 조회, 부정확한 행 수 추정치 및 누락된 인덱스를 확인합니다.
배포 또는 스키마 변경 후 쿼리가 느려지면 애플리케이션 코드를 변경하기 전에 쿼리 저장소 계획을 비교합니다. DBA는 기본 인덱스, 통계 또는 쿼리 셰이프 문제를 해결하는 동안 알려진 양호한 계획을 일시적으로 강제 적용할 수 있습니다.
쿼리 저장소 높은 CPU 시간 대신 대기를 표시하는 경우 병목 상태 식별을 사용하여 CPU, 메모리, 디스크 I/O, 연결 압력 및 차단 문제를 구분합니다.