훈훈훈

파이썬(Django) :: Select_related 와 Prefetch_related 본문

파이썬/Django

파이썬(Django) :: Select_related 와 Prefetch_related

훈훈훈 2020. 4. 13. 01:08

이번에는 Selected_related와 Prefetch_related에 대해 정리 해보려고 한다.

Django ORM으로 DB에 쿼리 시 get, filter, all 이외에 Selected_related와 Prefetch_related를 적절히 사용하면 아주 강력한 무기가 될 수 있다. 

 

이제 Selected_related와 Prefetch_related가 무엇인지 알아보도록 하자.

Selected related


# Selected_related 란 ?

Selected_related는 SQL Query 문의 JOIN 을 사용하여 foreign-key(one to one, many to one)를 사용하여 정참조할 때 사용하며 QuerySet을 가져올 때, 미리 related objects까지 불러오는 함수이다.

비록 쿼리는 복잡해지지만 불러온 data들은 데이터베이스 서버가 종료되기 전 까지 Cache에 남게되어 매 쿼리마다 DB에 접근하지 않아도 된다.   

 

# Why Selected_related ??

Selected_related를 사용함으로서 데이터베이스에 접근하는 빈도를 줄여 자원 낭비를 차단할 수 있음

 

# Example

하기 파일(models.py)에 City, Person, ID에 대한 테이블을 만듬

class City(models.Model):
    '''
    +----+---------+
    | id | name    |
    +----+---------+
    |  1 | Seoul   |
    |  2 | Tokyo   |
    |  3 | NewYork |
    +----+---------+
    '''
    name = models.CharField(max_length=50)

    class Meta:
        db_table = 'cities'

class Person(models.Model):
    '''
    +----+-------+--------------+
    | id | name  | city_id      |
    +----+-------+--------------+
    |  1 | Kim   |            1 |
    |  2 | Lee   |            1 |
    |  3 | Naoki |            2 |
    |  4 | 2Pac  |            3 |
    +----+-------+--------------+
    '''
    name = models.CharField(max_length=50)
    city = models.ForeignKey('City', on_delete=models.CASCADE)

    class Meta:
        db_table = 'persons'

class Identification(models.Model):
    '''
    +----+----------------+----------------+
    | id | identification | person_id      |
    +----+----------------+----------------+
    |  1 | 940101-1111111 |              1 |
    |  2 | 970101-1111111 |              2 |
    |  3 | 800101-1111111 |              3 |
    |  4 | 200101-1111111 |              4 |
    +----+----------------+----------------+
    '''
    identification = models.CharField(max_length=50)
    person         = models.ForeignKey('Person', on_delete=models.CASCADE)

    class Meta:
        db_table = 'identifications'

 

- Selected_related 사용 x

>>> Person.objects.get(id=1).city
<City: City object (1)>
>>> a = Person.objects.get(id=1)
>>> b = a.city
>>> b
<City: City object (1)>

--> Selected_related 함수를 사용하지 않을 시 위 명령어와 같이 매번 Person 클래스에서 City 클래스를 참조할떄마다 두번 씩 쿼리를 수행한다는 것을 알 수 있음 

 

 

- Selected_related 사용 0

>>> Person.objects.select_related('city').get(id=1)
<Person: Person object (1)>

--> 위 명령어를 보면 Selected_related 를 사용하지 않은 명령어보다 복잡해보이지만 한번 데이터베이스에 접근하여 Data를 가져오기 때문에 리소스를 훨씬 더 경제적으로 사용할 수 있다.

--> 물론 filter( )와 같은 명령어도 캐싱 기능이 있지만, Foreign_Key에 해당하는 참조 대상까지는 저장하지 않기 때문에 상황에 맞게 잘 사용하면 리소스를 효율적으로 사용할 수 있다.

 

추가적으로 아래와 같이 Person 클래스에서 city 칼럼이 참조하는 클래스를  바라본 후  ".key.[참조하고 있는 클래스의 칼럼]" 으로 DB로 쿼리를 할 수 있다.

>>> Person.objects.select_related('city').get(id=1).city.name
'Seoul'

 

Prefetch related 


# Prefetch_related 란?

prefetch_related는 selected_related와 같이 data를 Cache에 저장하며, 모든 relationships에서 사용이 가능하다.

 

# Selected_related  vs  Prefetch_related

Selected_related는 하나의 Query로 related Objects들을 불러오지만, Prefetch_related는 main query가 실행이 된 후 별도의 query가 실행이 된다. 따라서 1번 이상 쿼리가 진행되기 때문에 가급적 selected_related를 사용하는 것이 리소스 소모를 줄일 수 있다,

 

# Example

하기 파일(models.py)에 Person, Language, PersonLanguage 에 대한 테이블을 만듬

class Person(models.Model):
    '''
    +----+-------+--------------+
    | id | name  | city_name_id |
    +----+-------+--------------+
    |  1 | Kim   |            1 |
    |  2 | Lee   |            1 |
    |  3 | Naoki |            2 |
    |  4 | 2Pac  |            3 |
    +----+-------+--------------+
    '''
    name = models.CharField(max_length=50)
    city_name = models.ForeignKey('City', on_delete=models.CASCADE)
    language = models.ManyToManyField('Language', through='PersonLanguage')

    class Meta:
        db_table = 'persons'


class Language(models.Model):
    '''
    +----+----------+
    | id | name     |
    +----+----------+
    |  1 | Korean   |
    |  2 | japanese |
    |  3 | english  |
    +----+----------+
    '''
    name = models.CharField(max_length=50)

    class Meta:
        db_table = 'languages'

class PersonLanguage(models.Model):
    '''
     id | language_id | person_id |
    +----+-------------+-----------+
    |  1 |           1 |         1 |
    |  2 |           1 |         2 |
    |  3 |           2 |         3 |
    |  4 |           3 |         4 |
    |  5 |           2 |         4 |
    |  6 |           1 |         4 |
    +----+-------------+-----------+
    '''
    person   = models.ForeignKey(Person, on_delete=models.CASCADE)
    language = models.ForeignKey(Language, on_delete=models.CASCADE)

    class Meta:
        db_table = 'person_languages'

- Prefetch_related 사용 x

>>> Person.objects.get(id=4).language.values()
<QuerySet [{'id': 1, 'name': 'Korean'}, {'id': 2, 'name': 'japanese'}, {'id': 3, 'name': 'english'}]>
>>> a = Person.objects.get(id=4)
>>> b = a.language.values()
>>> b
<QuerySet [{'id': 1, 'name': 'Korean'}, {'id': 2, 'name': 'japanese'}, {'id': 3, 'name': 'english'}]>

--> Prefetch_related 함수를 사용하지 않을 시 위 명령어와 같이 매번 Person 클래스에서 language 클래스 many to many로 참조할떄마다 두번 씩 쿼리를 수행한다는 것을 알 수 있음 

 

 

- Prefetch_related 사용 0

>>> Person.objects.prefetch_related('language').get(id=4).language.values()
<QuerySet [{'id': 1, 'name': 'Korean'}, {'id': 2, 'name': 'japanese'}, {'id': 3, 'name': 'english'}]>

--> Selected_related 함수와 마찬가지로 쿼리문은 좀 더 길어지지만 many to many 관계를 대상으로 참조할때 Data를 참조 대상까지 Cache에 저장하기 때문에 해당 함수를 사용하길 권장한다.

 

 

   ** 추가내용

      - Prefetch_related로 역참조 시 lookup에  '_set' 을 사용

Comments