← 블로그 홈

Django ORM 최적화: select_related()와 prefetch_related() 완전 정복

Django 개발에서 가장 흔히 마주치는 성능 문제 중 하나가 바로 N+1 쿼리 문제입니다. 이를 해결하기 위해 Django ORM은 select_related()와 prefetch_related() 두 가지 강력한 도구를 제공합니다. 이 글에서는 두 메서드의 차이점과...

Django 개발에서 가장 흔히 마주치는 성능 문제 중 하나가 바로 N+1 쿼리 문제입니다. 이를 해결하기 위해 Django ORM은 select_related()prefetch_related() 두 가지 강력한 도구를 제공합니다. 이 글에서는 두 메서드의 차이점과 고급 최적화 기법을 심도 있게 살펴보겠습니다.

🔍 N+1 쿼리 문제란?

먼저 N+1 쿼리 문제가 무엇인지 이해해보겠습니다.

# 모델 정의
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.CharField(max_length=100)
    bio = models.TextField()

class Publisher(models.Model):
    name = models.CharField(max_length=100)
    address = models.TextField()

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
    publication_date = models.DateField()
    isbn = models.CharField(max_length=13)

class Review(models.Model):
    book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='reviews')
    reviewer_name = models.CharField(max_length=100)
    rating = models.IntegerField()
    comment = models.TextField()

N+1 쿼리 문제 발생 예제

# 문제가 되는 코드
def get_books_with_authors():
    books = Book.objects.all()  # 1번의 쿼리
    
    for book in books:
        print(f"{book.title} - {book.author.name}")  # 각 책마다 추가 쿼리 발생
        # N번의 추가 쿼리 (N = 책의 개수)

# 총 1 + N번의 쿼리가 실행됨
# 책이 1000권이면 1001번의 쿼리!
-- 실제 실행되는 SQL
SELECT * FROM book;  -- 1번째 쿼리

SELECT * FROM author WHERE id = 1;  -- 2번째 쿼리
SELECT * FROM author WHERE id = 2;  -- 3번째 쿼리
SELECT * FROM author WHERE id = 3;  -- 4번째 쿼리
-- ... 각 책마다 반복

기본 개념과 동작 원리

select_related()SQL JOIN을 사용하여 관련 객체를 한 번의 쿼리로 가져옵니다.

# select_related() 사용
def get_books_with_authors_optimized():
    books = Book.objects.select_related('author')  # 1번의 JOIN 쿼리
    
    for book in books:
        print(f"{book.title} - {book.author.name}")  # 추가 쿼리 없음

# 총 1번의 쿼리만 실행됨!
-- 실제 실행되는 SQL (INNER JOIN 사용)
SELECT 
    book.id, book.title, book.publication_date, book.isbn,
    author.id, author.name, author.email, author.bio
FROM book 
INNER JOIN author ON book.author_id = author.id;

1. 단일 관계 최적화

# 기본 사용법
books = Book.objects.select_related('author')
books = Book.objects.select_related('publisher')

# 여러 관계 동시 최적화
books = Book.objects.select_related('author', 'publisher')

2. 중첩 관계 최적화

# 모델 확장 예제
class AuthorProfile(models.Model):
    author = models.OneToOneField(Author, on_delete=models.CASCADE)
    birth_date = models.DateField()
    nationality = models.CharField(max_length=50)

# 중첩 관계 최적화
books = Book.objects.select_related(
    'author',
    'author__authorprofile',  # 이중 관계 최적화
    'publisher'
)

for book in books:
    print(f"{book.title} by {book.author.name}")
    print(f"Nationality: {book.author.authorprofile.nationality}")
    # 모든 데이터가 이미 로드되어 있어 추가 쿼리 없음
# 동적으로 select_related 적용
def get_books(include_author=False, include_publisher=False):
    queryset = Book.objects.all()
    
    select_fields = []
    if include_author:
        select_fields.append('author')
    if include_publisher:
        select_fields.append('publisher')
    
    if select_fields:
        queryset = queryset.select_related(*select_fields)
    
    return queryset
import time
from django.db import connection

def measure_query_performance():
    # 비최적화 버전
    start_time = time.time()
    books = Book.objects.all()
    for book in books:
        _ = book.author.name  # N+1 쿼리 발생
    
    unoptimized_time = time.time() - start_time
    unoptimized_queries = len(connection.queries)
    
    # 초기화
    connection.queries.clear()
    
    # 최적화 버전
    start_time = time.time()
    books = Book.objects.select_related('author')
    for book in books:
        _ = book.author.name  # 추가 쿼리 없음
    
    optimized_time = time.time() - start_time
    optimized_queries = len(connection.queries)
    
    print(f"비최적화: {unoptimized_time:.3f}초, {unoptimized_queries}개 쿼리")
    print(f"최적화: {optimized_time:.3f}초, {optimized_queries}개 쿼리")
    print(f"성능 향상: {unoptimized_time/optimized_time:.1f}배")

# 결과 예시:
# 비최적화: 2.345초, 1001개 쿼리
# 최적화: 0.089초, 1개 쿼리  
# 성능 향상: 26.3배

기본 개념과 동작 원리

prefetch_related()별도의 쿼리로 관련 객체들을 가져온 후 Python에서 관계를 연결합니다.

# ManyToMany 관계 모델 추가
class Tag(models.Model):
    name = models.CharField(max_length=50)

class Book(models.Model):
    # 기존 필드들...
    tags = models.ManyToManyField(Tag, related_name='books')

# prefetch_related() 사용
def get_books_with_tags():
    books = Book.objects.prefetch_related('tags')  # 2번의 쿼리
    
    for book in books:
        tag_names = [tag.name for tag in book.tags.all()]  # 추가 쿼리 없음
        print(f"{book.title}: {', '.join(tag_names)}")
-- 실제 실행되는 SQL (2개의 분리된 쿼리)
-- 첫 번째 쿼리: 책 정보
SELECT * FROM book;

-- 두 번째 쿼리: 태그 정보 (IN 절 사용)
SELECT 
    book_tags.book_id, tag.id, tag.name
FROM tag
INNER JOIN book_tags ON tag.id = book_tags.tag_id
WHERE book_tags.book_id IN (1, 2, 3, 4, 5, ...);

1. 역참조 관계 최적화

# 저자별 책 목록 조회
authors = Author.objects.prefetch_related('book_set')

for author in authors:
    books = author.book_set.all()  # 추가 쿼리 없음
    print(f"{author.name}: {books.count()}권의 책")

# related_name 사용 시
class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')

authors = Author.objects.prefetch_related('books')

2. 중첩 관계 최적화

# 책 → 리뷰 → 리뷰어 정보까지 최적화
books = Book.objects.prefetch_related(
    'reviews',  # 책의 리뷰들
    'reviews__reviewer'  # 각 리뷰의 리뷰어 (추가 모델 필요)
)

for book in books:
    reviews = book.reviews.all()
    for review in reviews:
        print(f"{review.reviewer.name}: {review.rating}점")

3. 필터링과 함께 사용

# 특정 조건의 관련 객체만 prefetch
from django.db import models

books = Book.objects.prefetch_related(
    models.Prefetch(
        'reviews',
        queryset=Review.objects.filter(rating__gte=4)  # 4점 이상 리뷰만
    )
)

for book in books:
    high_reviews = book.reviews.all()  # 이미 필터링된 결과
    print(f"{book.title}: {high_reviews.count()}개의 고평점 리뷰")

🚀 Prefetch 객체를 사용한 고급 최적화

Prefetch 객체의 강력한 기능

Prefetch 객체를 사용하면 prefetch 동작을 세밀하게 제어할 수 있습니다.

from django.db.models import Prefetch, Count, Avg

# 기본 Prefetch 객체 사용
books = Book.objects.prefetch_related(
    Prefetch('reviews')  # 기본 prefetch_related('reviews')와 동일
)

1. 커스텀 QuerySet으로 최적화

# 특정 조건으로 필터링된 관련 객체만 가져오기
def get_books_with_recent_reviews():
    from datetime import date, timedelta
    
    recent_date = date.today() - timedelta(days=30)
    
    books = Book.objects.prefetch_related(
        Prefetch(
            'reviews',
            queryset=Review.objects.filter(
                created_at__gte=recent_date
            ).select_related('reviewer'),  # 리뷰어 정보도 함께
            to_attr='recent_reviews'  # 커스텀 속성명
        )
    )
    
    for book in books:
        # book.reviews.all() 대신 book.recent_reviews 사용
        reviews = book.recent_reviews
        print(f"{book.title}: {len(reviews)}개의 최근 리뷰")

2. 정렬된 관련 객체 가져오기

# 평점 순으로 정렬된 리뷰 prefetch
def get_books_with_sorted_reviews():
    books = Book.objects.prefetch_related(
        Prefetch(
            'reviews',
            queryset=Review.objects.order_by('-rating', '-created_at'),
            to_attr='sorted_reviews'
        )
    )
    
    for book in books:
        top_reviews = book.sorted_reviews[:5]  # 상위 5개 리뷰
        for review in top_reviews:
            print(f"  {review.rating}점: {review.comment[:50]}...")

3. 집계 함수와 함께 사용

# 리뷰 통계를 포함한 책 정보 조회
def get_books_with_review_stats():
    books = Book.objects.annotate(
        review_count=Count('reviews'),
        avg_rating=Avg('reviews__rating')
    ).prefetch_related(
        Prefetch(
            'reviews',
            queryset=Review.objects.select_related('reviewer').order_by('-rating')
        )
    )
    
    for book in books:
        print(f"{book.title}")
        print(f"  평균 평점: {book.avg_rating:.1f}")
        print(f"  리뷰 수: {book.review_count}")
        
        # 실제 리뷰 데이터도 이미 로드됨
        best_review = book.reviews.all()[0] if book.reviews.all() else None
        if best_review:
            print(f"  최고 평점 리뷰: {best_review.rating}점")

4. 중첩된 Prefetch 최적화

# 복잡한 중첩 관계 최적화
def get_authors_with_detailed_books():
    authors = Author.objects.prefetch_related(
        Prefetch(
            'books',
            queryset=Book.objects.select_related('publisher').prefetch_related(
                Prefetch(
                    'reviews',
                    queryset=Review.objects.filter(rating__gte=4).order_by('-rating'),
                    to_attr='good_reviews'
                ),
                'tags'
            ),
            to_attr='published_books'
        )
    )
    
    for author in authors:
        print(f"\n저자: {author.name}")
        for book in author.published_books:
            print(f"  책: {book.title} (출판사: {book.publisher.name})")
            print(f"    태그: {', '.join(tag.name for tag in book.tags.all())}")
            print(f"    좋은 리뷰: {len(book.good_reviews)}개")

5. 조건부 Prefetch

# 동적으로 Prefetch 조건 변경
def get_books_dynamic_prefetch(min_rating=None, tag_filter=None):
    prefetch_list = []
    
    # 조건부 리뷰 prefetch
    if min_rating:
        review_queryset = Review.objects.filter(rating__gte=min_rating)
        prefetch_list.append(
            Prefetch('reviews', queryset=review_queryset, to_attr='filtered_reviews')
        )
    else:
        prefetch_list.append('reviews')
    
    # 조건부 태그 prefetch
    if tag_filter:
        tag_queryset = Tag.objects.filter(name__icontains=tag_filter)
        prefetch_list.append(
            Prefetch('tags', queryset=tag_queryset, to_attr='filtered_tags')
        )
    else:
        prefetch_list.append('tags')
    
    books = Book.objects.prefetch_related(*prefetch_list)
    return books

⚡ 성능 비교와 최적화 전략

1. 쿼리 수 비교

from django.test.utils import override_settings
from django.db import connection

@override_settings(DEBUG=True)
def compare_query_strategies():
    """다양한 최적화 전략의 쿼리 수 비교"""
    
    strategies = {
        '최적화 없음': lambda: Book.objects.all(),
        'select_related만': lambda: Book.objects.select_related('author', 'publisher'),
        'prefetch_related만': lambda: Book.objects.prefetch_related('reviews', 'tags'),
        '둘 다 사용': lambda: Book.objects.select_related('author', 'publisher').prefetch_related('reviews', 'tags'),
        'Prefetch 객체': lambda: Book.objects.select_related('author').prefetch_related(
            Prefetch('reviews', queryset=Review.objects.select_related('reviewer'))
        )
    }
    
    for name, strategy in strategies.items():
        connection.queries.clear()
        
        books = strategy()
        
        # 실제 데이터 접근
        for book in books[:10]:  # 처음 10권만 테스트
            _ = book.title
            _ = book.author.name
            _ = book.publisher.name
            _ = list(book.reviews.all())
            _ = list(book.tags.all())
        
        query_count = len(connection.queries)
        print(f"{name}: {query_count}개 쿼리")

# 결과 예시:
# 최적화 없음: 41개 쿼리
# select_related만: 21개 쿼리  
# prefetch_related만: 23개 쿼리
# 둘 다 사용: 3개 쿼리
# Prefetch 객체: 3개 쿼리

2. 메모리 사용량 최적화

# 대량 데이터 처리 시 메모리 최적화
def process_large_dataset():
    # 잘못된 방법: 모든 데이터를 메모리에 로드
    books = Book.objects.select_related('author').prefetch_related('reviews')
    all_books = list(books)  # 메모리 부족 위험
    
    # 올바른 방법: iterator() 사용
    books = Book.objects.select_related('author').prefetch_related('reviews')
    
    for book in books.iterator(chunk_size=1000):
        # 1000개씩 청크 단위로 처리
        process_book(book)

def process_book_batch():
    # 배치 처리 최적화
    from django.core.paginator import Paginator
    
    books = Book.objects.select_related('author').prefetch_related('reviews')
    paginator = Paginator(books, 100)  # 100개씩 페이지네이션
    
    for page_num in paginator.page_range:
        page = paginator.page(page_num)
        for book in page.object_list:
            process_book(book)

3. 캐싱과 결합한 최적화

from django.core.cache import cache
from django.db.models import Prefetch

def get_cached_books_with_reviews():
    cache_key = 'books_with_reviews_v1'
    
    # 캐시에서 먼저 확인
    cached_data = cache.get(cache_key)
    if cached_data:
        return cached_data
    
    # 캐시 미스 시 최적화된 쿼리 실행
    books = Book.objects.select_related('author', 'publisher').prefetch_related(
        Prefetch(
            'reviews',
            queryset=Review.objects.filter(rating__gte=3).order_by('-rating')[:5],
            to_attr='top_reviews'
        ),
        'tags'
    )
    
    # 직렬화 가능한 형태로 변환
    book_data = []
    for book in books:
        book_info = {
            'title': book.title,
            'author': book.author.name,
            'publisher': book.publisher.name,
            'tags': [tag.name for tag in book.tags.all()],
            'top_reviews': [
                {'rating': review.rating, 'comment': review.comment}
                for review in book.top_reviews
            ]
        }
        book_data.append(book_info)
    
    # 30분 캐싱
    cache.set(cache_key, book_data, 1800)
    return book_data

🎯 실전 최적화 패턴

1. API 엔드포인트 최적화

# Django REST Framework 뷰 최적화
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action

class BookViewSet(ModelViewSet):
    def get_queryset(self):
        queryset = Book.objects.select_related('author', 'publisher')
        
        # 액션별로 다른 최적화 적용
        if self.action == 'list':
            # 목록 조회 시 기본 정보만
            queryset = queryset.prefetch_related('tags')
        elif self.action == 'retrieve':
            # 상세 조회 시 모든 관련 정보
            queryset = queryset.prefetch_related(
                Prefetch(
                    'reviews',
                    queryset=Review.objects.select_related('reviewer').order_by('-created_at')
                ),
                'tags'
            )
        
        return queryset
    
    @action(detail=False, methods=['get'])
    def with_stats(self, request):
        """통계 정보가 포함된 책 목록"""
        books = Book.objects.select_related('author').annotate(
            review_count=Count('reviews'),
            avg_rating=Avg('reviews__rating')
        ).prefetch_related(
            Prefetch(
                'reviews',
                queryset=Review.objects.order_by('-rating')[:3],
                to_attr='top_3_reviews'
            )
        )
        
        # 직렬화 및 응답
        return Response(self.get_serializer(books, many=True).data)

2. 관리자 인터페이스 최적화

# Django Admin 최적화
from django.contrib import admin

@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ['title', 'author_name', 'publisher_name', 'review_count']
    list_select_related = ['author', 'publisher']  # list_display 최적화
    
    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        return queryset.select_related('author', 'publisher').annotate(
            review_count=Count('reviews')
        )
    
    def author_name(self, obj):
        return obj.author.name
    author_name.short_description = '저자'
    author_name.admin_order_field = 'author__name'
    
    def publisher_name(self, obj):
        return obj.publisher.name
    publisher_name.short_description = '출판사'
    
    def review_count(self, obj):
        return obj.review_count
    review_count.short_description = '리뷰 수'
    review_count.admin_order_field = 'review_count'

@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
    list_display = ['name', 'book_count']
    
    def get_queryset(self, request):
        return super().get_queryset(request).annotate(
            book_count=Count('books')
        )
    
    def book_count(self, obj):
        return obj.book_count
    book_count.short_description = '저서 수'

3. 복잡한 검색 최적화

def advanced_book_search(query_params):
    """복잡한 조건의 책 검색 최적화"""
    
    # 기본 쿼리셋
    books = Book.objects.select_related('author', 'publisher')
    
    # 동적 필터링
    if query_params.get('author_name'):
        books = books.filter(author__name__icontains=query_params['author_name'])
    
    if query_params.get('min_rating'):
        books = books.filter(reviews__rating__gte=query_params['min_rating'])
    
    if query_params.get('tags'):
        tag_names = query_params['tags'].split(',')
        books = books.filter(tags__name__in=tag_names).distinct()
    
    # 최종 최적화
    books = books.prefetch_related(
        Prefetch(
            'reviews',
            queryset=Review.objects.select_related('reviewer').order_by('-rating')[:5],
            to_attr='top_reviews'
        ),
        'tags'
    ).annotate(
        avg_rating=Avg('reviews__rating'),
        review_count=Count('reviews')
    ).order_by('-avg_rating', '-review_count')
    
    return books

⚠️ 주의사항과 함정

# 잘못된 사용: 불필요한 JOIN
# 모든 필드를 항상 select_related 하지 말 것
books = Book.objects.select_related()  # 모든 ForeignKey 관계를 JOIN (위험!)

# 올바른 사용: 필요한 관계만 명시
books = Book.objects.select_related('author')  # 실제로 사용하는 관계만

# 주의: 깊은 중첩은 성능 저하 가능
books = Book.objects.select_related(
    'author__profile__address__country'  # 너무 깊은 중첩
)
# 잘못된 사용: prefetch 후 추가 필터링
books = Book.objects.prefetch_related('reviews')
for book in books:
    # 이렇게 하면 prefetch 효과가 사라짐!
    recent_reviews = book.reviews.filter(created_at__gte=recent_date)

# 올바른 사용: Prefetch 객체로 미리 필터링
books = Book.objects.prefetch_related(
    Prefetch(
        'reviews',
        queryset=Review.objects.filter(created_at__gte=recent_date),
        to_attr='recent_reviews'
    )
)
for book in books:
    recent_reviews = book.recent_reviews  # 추가 쿼리 없음

3. 메모리 사용량 고려

# 위험: 대량 데이터 prefetch
# 10,000권의 책에 각각 100개씩 리뷰가 있다면?
books = Book.objects.prefetch_related('reviews')  # 1,000,000개 리뷰 로드!

# 안전: 필요한 만큼만 prefetch
books = Book.objects.prefetch_related(
    Prefetch(
        'reviews',
        queryset=Review.objects.order_by('-rating')[:10],  # 상위 10개만
        to_attr='top_reviews'
    )
)

📊 성능 모니터링과 디버깅

1. 쿼리 분석 도구

# Django Debug Toolbar 설정
# settings.py
if DEBUG:
    INSTALLED_APPS += ['debug_toolbar']
    MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
    INTERNAL_IPS = ['127.0.0.1']

# 커스텀 쿼리 분석기
class QueryAnalyzer:
    def __init__(self):
        self.queries = []
    
    def __enter__(self):
        from django.db import connection
        self.initial_count = len(connection.queries)
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        from django.db import connection
        self.queries = connection.queries[self.initial_count:]
        self.analyze()
    
    def analyze(self):
        print(f"\n{len(self.queries)}개의 쿼리 실행")
        
        for i, query in enumerate(self.queries, 1):
            print(f"\n쿼리 {i}: {query['time']}초")
            print(f"SQL: {query['sql'][:100]}...")
            
            # 느린 쿼리 감지
            if float(query['time']) > 0.1:
                print("⚠️  느린 쿼리 감지!")

# 사용 예제
with QueryAnalyzer():
    books = Book.objects.select_related('author').prefetch_related('reviews')
    for book in books[:10]:
        print(f"{book.title} by {book.author.name}")
        print(f"Reviews: {book.reviews.count()}")

2. 성능 테스트 자동화

import time
from django.test import TestCase
from django.test.utils import override_settings

class PerformanceTestCase(TestCase):
    @classmethod
    def setUpTestData(cls):
        # 테스트 데이터 생성
        cls.create_test_data()
    
    def test_book_query_performance(self):
        """책 조회 성능 테스트"""
        
        test_cases = [
            ('기본 쿼리', lambda: Book.objects.all()),
            ('select_related', lambda: Book.objects.select_related('author')),
            ('prefetch_related', lambda: Book.objects.prefetch_related('reviews')),
            ('최적화 조합', lambda: Book.objects.select_related('author').prefetch_related('reviews'))
        ]
        
        for name, query_func in test_cases:
            with self.subTest(name=name):
                start_time = time.time()
                
                books = query_func()
                # 실제 데이터 접근
                for book in books:
                    _ = book.title
                    _ = book.author.name
                    _ = list(book.reviews.all())
                
                execution_time = time.time() - start_time
                
                print(f"{name}: {execution_time:.3f}초")
                
                # 성능 기준 검증
                if name == '최적화 조합':
                    self.assertLess(execution_time, 0.1, "최적화된 쿼리가 너무 느림")

🎯 결론 및 모범 사례

선택 가이드라인

관계 유형 권장 방법 이유
ForeignKey select_related() JOIN으로 단일 쿼리
OneToOneField select_related() JOIN으로 단일 쿼리
ManyToManyField prefetch_related() 별도 쿼리가 더 효율적
역참조 (ForeignKey) prefetch_related() 1:N 관계 처리
중첩 관계 조합 사용 관계 유형에 따라 선택

최적화 체크리스트

# ✅ 모범 사례 체크리스트

def optimized_book_view():
    # 1. 필요한 관계만 최적화
    books = Book.objects.select_related(
        'author',  # 항상 필요한 ForeignKey
        'publisher'  # 항상 필요한 ForeignKey
    ).prefetch_related(
        # 2. Prefetch 객체로 세밀한 제어
        Prefetch(
            'reviews',
            queryset=Review.objects.select_related('reviewer').order_by('-rating')[:5],
            to_attr='top_reviews'
        ),
        # 3. 단순한 ManyToMany는 기본 prefetch
        'tags'
    ).annotate(
        # 4. 집계는 DB에서 처리
        avg_rating=Avg('reviews__rating'),
        review_count=Count('reviews')
    ).order_by('-avg_rating')
    
    return books

# ❌ 피해야 할 안티패턴
def bad_book_view():
    books = Book.objects.all()  # 최적화 없음
    
    for book in books:
        book.author.name  # N+1 쿼리
        book.reviews.count()  # 매번 COUNT 쿼리
        book.reviews.filter(rating__gte=4)  # 매번 필터 쿼리

실무 권장사항

  1. 개발 단계에서 Django Debug Toolbar 필수 사용
  2. 복잡한 쿼리는 Raw SQL 프로파일링으로 검증
  3. 대량 데이터는 Pagination과 함께 최적화
  4. 캐싱 레이어와 조합하여 성능 극대화
  5. 정기적인 성능 모니터링과 최적화

Django ORM의 select_related()prefetch_related()를 제대로 활용하면 N+1 쿼리 문제를 해결하고 데이터베이스 성능을 극적으로 향상시킬 수 있습니다. 특히 Prefetch 객체를 사용한 고급 최적화 기법은 복잡한 쿼리 최적화의 핵심입니다.

다음 글에서는 Django의 쿼리 최적화를 더욱 심화시킨 Raw SQL과 Database Index 활용법을 다루겠습니다. 🚀

이 글을 공유해보세요!