• Django select_related를 알아보자

    2021. 7. 9.

    by. Jacob Lee

    728x90

     


     

    Select_related()

    이 전에 잠깐 공부한 적이 있는 select_related인데 깊게 공부하지 못 했던 것 같아 장고 공식문서를 보며 한 번 다시 공부해보려 한다. 

     

    Select_related()란?

    select_related()는 쿼리가 실행될 때, 추가적인 관련 객체 데이터를 선택하고, 외래키 관계를 따르는 쿼리셋을 반환한다. 즉, 퍼포먼스 부스터의 역할을 하는데, 사용함으로써 당장은 보다 복잡한 쿼리를 만들지만, 나중에 외래키 관계를 사용할 때 추가적인 데이터베이스 쿼리를 필요로 하지 않는다는 장점이 있다.

     

    다음 예제는 평범한 쿼리 조회와 select_related() 조회가 어떻게 다른지를 설명해준다.

    기본 조회는 다음과 같다.

    # 데이터 베이스 요청
    e = Entry.objects.get(id=5)
    
    # 블로그 객체와 관련있는 값을 얻기 위해 다시 데이터베이스를 요청
    b = e.blog

     

    다음은 select_realted()를 사용한 조회 예제이다

    # 데이터베이스 요청
    e = Entry.objects.select_related('blog').get(id=5)
    
    # 이 부분에서는 데이터베이스에 요청하지 하지 않는다
    # 왜냐면 e.blog는 전 쿼리에서 미리 채워져(가져왔기-prepopulated)있기 때문이다
    # Doesn't hit the database, because e.blog has been prepopulated
    # in the previous query.
    b = e.blog

     

    select_related()는 객체의 어느 쿼리셋과도 사용할 수 있다.

    from django.utils import timezone
    
    # 향후 퍼블리시 되기로 예정되어 있는 블로그 각 항목들을 찾음
    blogs = set()
    
    for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
        # 만약 select_related()를 사용하지 않았다면, 이 반복문은 각 반복마다 데이터베이스 쿼리를 사용한다.
        # 각 항목에 관련 블로그를 가져오기 위한 루프 반복
        blogs.add(e.blog)

     

    filter()select_related() 체이닝 순서는 중요하지 않다. 두 쿼리셋은 동일하다.

    Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
    Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())

     

    다음과 같은 모델이 있는 경우, 외부키를 조회할 때와 비슷한 방법으로 외부키를 따를 수 있다.

    from django.db import models
    
    class City(models.Model):
        # ...
        pass
    
    class Person(models.Model):
        # ...
        hometown = models.ForeignKey(
            City,
            on_delete=models.SET_NULL,
            blank=True,
            null=True,
        )
    
    class Book(models.Model):
        # ...
        author = models.ForeignKey(Person, on_delete=models.CASCADE)

     

    그러고나서 Book.objects.select_related('author__hometown').get(id=4)를 호출함으로써 관련 Person 모델과 City 모델을 캐싱이 된다.

    # author과 hometown 테이블에 조인을 사용함으로써 한 번의 데이터베이스 요청을 보낸다.
    b = Book.objects.select_related('author__hometown').get(id=4)
    p = b.author         # 데이터베이스 요청 없음
    c = p.hometown       # 데이터베이스 요청 없음
    
    # select_related()를 사용하지 않는다면...
    b = Book.objects.get(id=4)  # 데이터베이스 요청(1)
    p = b.author         # 데이터베이스 요청(2)
    c = p.hometown       # 데이터베이스 요청(3)

     

    이와 같이 select_related()에 전달된 필드 목록에서 외래키일대일 관계를 참조할 수 있다.

    또한, 전달된 필드 목록에서 역방향의 일대일 관계에도 참조할 수 있다 - 즉, 일대일 관계에서 다시 필드가 정의된 객체로 돌아갈 수 있다는 것이다.

    필드 이름을 지정하는 대신, 관련 객체의 필드에  related_name을 사용할 수 있다.

     

    select_related()를 사용하다보면 select_related()를 다수의 관련 객체와 호출한다거나, 모든 관계를 인지하지 못한 상황이 있을 수가 있다. 이러한 경우 인자 없이 select_related()를 호출하는 것이 가능하다. 그렇게 하면, 찾을 수 있는 모든 non-null 외래키들이 선택되어진다(nullable 외래키는 반드시 명시되어야 한다).

     

    사실 이는 기본 쿼리를 더욱 복잡하게 만든다거나, 실제 필요한 데이터보다 더욱 많은 양의 데이터를 반환할 수 있기 때문에 대부분의 상황에서는 권장되지 않는 방법이다.

     

    만약 쿼리셋에서 과거에 select_related를 호출함으로써 추가된 관련 필드의 리스트를 지워야하는 경우, None을 매개변수로 전달해줄 수 있다.

    >>> without_relations = queryset.select_related(None)

    select_related를 이어서 호출하는 방식은 다른 메소드와 유사한 방식으로 작동한다.

    즉, select_related('foo', 'bar')select_related('foo').select_related('bar')와 동일하다.

     

    select_related만으로도 글이 길어진 것 같아, 다음 글에서 prefetch_related를 다뤄볼까 한다.

     

    Reference

     

    728x90

    댓글