Django - select_related와prefetch_related

김현우·2020년 8월 29일
10

django

목록 보기
5/6

Django ORM 사용할 때 Query 개수를 줄일 수 있는 방법 중 select_related, prefetch_related에 대하여 알아보자!😃

select_related, prefetch_related
하나의 QuerySet을 가져올 때, 미리 related objects들까지 다 불러와주는 함수

query는 복잡하게 만들지만, 불러온 데이터들은 모두 캐시에 남아있게 되므로 DB에 다시 접근해야 하는 수고를 덜어줄 수 있다.😞

두 메서드 모두 DB에 접근하는 횟수, 즉 쿼리의 개수를 줄임으로써 성능향상을 기대할 수 있다!

ORM, QuerySet이란?

출처 : Django Book(https://djangobook.com/)


ORM은 객체(Object)와 관계형 데이터베이스(Relational Database)의 데이터를 매핑(Mapping)해주는 것을 의미한다. 즉, 데이터 베이스와 객체지향 프로그래밍 언어 간의 호환되지 않는 데이터를 변환하는 프로그래밍 기법이다.

select_releated

언제 사용하는가?

  • 객체가 역참조하는 single object(one-to-one or many-to-one)이거나, 또는 정참조 foreign key 일 때 사용
  • 각각의 lookup마다 SQL의JOIN을 실행하여 테이블의 일부를 가져오고, select .. from에서 관련된 필드들을 가져옴

어떻게 수행되는가?

  • DB 단에서 INNER JOIN 으로 쿼리수행

형식

  • parameter에는 참조하는 class의 이름을 소문자로 쓰고 ' '에 감싼다.
    ex) 다음과 같이 소문자로 class 이름을 써준다.
wish_list = WishList.objects.filter(user_id= 1 ).select_related('product', 'product__name')

언제 사용하는가?

  • 객체가 정참조 multiple objects(many-to-many or one-to-many)이거나, 또는 역참조 Foreign Key일때 사용

어떻게 수행되는가?

  • 각 관계 별로 DB 쿼리를 수행하고, 파이썬 단에서 조인을 수행

형식

  • <lowercase 모델이름>_set : 장고가 지어주는 default 역참조 메서드 이름. ForeignKey 필드에 이름을 정하면 default이름 대신 정해진 이름을 쓸 수 있다.
class Image(models.Model):
    product = models.ForeignKey(Product, related_name = 'images')

# related_name에 따라
 product.images.all()

ForeignKey와 정참조와 역참조

ForeignKey를 가진 클래스에서 가지지 않는 클래스를 참조할 때는 정참조
ForeignKey를 가지지 않은 클래스에서 가진 클래스를 참조할 때는 역참조

class ExampleA(models.Model):
    pass

class ExampleB(models.Model):
    a = ForeignKey(ExampleA)
    
ExampleB.objects.select_related('a').all() # 정참조
ExampleA.objects.prefetch_related('exampleb_set').all() # 역참조

배운 것을 직접 사용하여 보자!!

Product의 모델

class ProductName(models.Model):
    name = models.CharField(max_length = 200)

    class Meta:
        db_table = "product_names"

    def __str__(self):
        return self.name

class Product(models.Model):
    name            = models.ForeignKey(ProductName, on_delete = models.CASCADE)
    description     = models.CharField(max_length = 500)
    price           = models.IntegerField()
    is_new          = models.BooleanField(null = True)
    sub_category    = models.ForeignKey('SubCategory', on_delete = models.CASCADE)
    product_color   = models.ManyToManyField('Color', through = 'ProductColor')
    product_url     = models.URLField(max_length=2000, null=True)

    class Meta:
        db_table = "products"

class Color(models.Model):
    name          = models.CharField(max_length = 200)
    color_image   = models.URLField(max_length = 2000)

    class Meta:
        db_table = "colors"

    def __str__(self):
        return self.name

class ProductColor(models.Model):
    product = models.ForeignKey(Product, on_delete = models.CASCADE)
    color = models.ForeignKey(Color, on_delete = models.CASCADE)

    class Meta:
        db_table = "product_colors"

class Image(models.Model):
    image_url     = models.URLField(max_length = 2000)
    product       = models.ForeignKey('Product', on_delete = models.CASCADE, null = True)

    class Meta:
        db_table = "images"

    def __str__(self):
        return self.image_url

WishList의 모델

class WishList(models.Model):
    user         = models.ForeignKey('User', on_delete = models.CASCADE)
    product      = models.ForeignKey('product.Product', on_delete = models.CASCADE)
    count        = models.IntegerField(default = 1, help_text = "제품개수")

    class Meta:
        db_table = "user_wishlists"

내가 해볼 것은 wishlist에 있는 product의 정보들을 가져오는 것이다.

이때 select_relatedprefetch_related를 사용하여야 Query 개수를 줄이면서 DB hit을 적게 할 수 있다!

우선 관계부터 정렬하여 보자.

wishlist에서 productproduct의 name에 접근하기 위하여 select_related,
many-to-many관계인 product의 color, product의 one-to-many관계image에 접근하기 위하여 prefetch_related를 사용하였다!

코드는 다음과 같다!

wish_list = WishList.objects.filter(user_id=request.user.id)
        .select_related('product', 'product__name')
        .prefetch_related('product__product_color', 'product__image_set')

전체코드

class WishListView(View):
    @login_required
    def get(self, request):
    
        wish_list = WishList.objects.filter(user_id=request.user.id)
        .select_related('product', 'product__name')
        .prefetch_related('product__product_color', 'product__image_set')
        
        result = [{
            "id": wish.product.id,
            "name": wish.product.name.name,
            "option": [color.name for color in wish.product.product_color.all()],
            'img': [image.image_url for image in wish.product.image_set.all()],
            "quantity": wish.count,
            "price": wish.product.price
        } for wish in wish_list]
        return JsonResponse({'result' : result}) 
profile
코딩을 잘하는 개발자가 되자!

2개의 댓글

comment-user-thumbnail
2021년 4월 18일

안녕하세요! 글 잘봤습니다. 혹시 글 내부에 삽입된 관계도 뭘로 그리셨을까요?

답글 달기
comment-user-thumbnail
2022년 1월 28일

핵심만 정리한 글 감사합니다.

답글 달기