Django

[django] ORM Query Set 구조와 원리 그리고 최적화 전략

a-몬드 2022. 4. 27. 10:07
반응형

아래의 강의를 듣다가 요약정리 해봤다.

https://www.youtube.com/watch?v=EZgLfDrUlrk 

1. QuerySet을 통해 알아보는 ORM 특징

- Lazy Loading 지연로딩 : 정말 필요한 시점에 SQL을 호출한다.


#예시 views.py

#User를 선언하는 시점에는 퀴리셋에 지나지 않고 실제로 SQL호출을 하지 않는다.
query_set = User.objects.all() 

#list()로 쿼리셋을 호출하였을때 실제 SQL이 실행된다.
user_list = list(query_set)


- 지연 로딩의 단점

불필요하게 SQL이 두번 호출된다.


query = User.objects.all()

User = query[0]#한명의 데이터를 가져오기 위해서 실행될 SQL문에 limit 1을 주어 한번 실행하고

list_user= list(query) #또 다시 목록을 얻기 위해 다시 SQL문을 호출한다.

#비효율 적이다.


- Caching : QuerySet 캐싱을 재사용

쿼리셋을 호출하는 순서가 바뀌는것 만으로도 QuerySet캐싱 때문에 발생하는 SQL이 달라질 수 있다.


query = User.objects.all()

list_user= list(query) #전체 목록을 얻기 위해 SQL문을 호출

#바로 위에서 모든 목록을 가져오는 SQL을 이미 호출하였으므로 User쿼리셋에서 캐싱된 값을 재활용(SQL호출 X)
User = query[0]

 


-Eager Loading 즉시로딩 : N+1 Problem

user가 100명이라면 SQL은 100+1번 호출된다.


query = User.objects.all()

for user in query:
    #for문에서의 user는 user중에 한명이다
    user.userinfo
    #한명의 정보를 가져오기 위해 sql쿼리문 호출 되고 for문이 돌때마다 호출된다.


해결법 

selected_related()와 prefetch_related라는 메서드 사용

 

selected_related() : 조인

prefetch_related() : 추가 쿼리


#views.py

Model.objects
        .filer(조건)
        .select_related('정방향 참조')
        .prefetch_related('역방향 참조')
        
#sql
select * from 'Model' m
(inner or left outer) join '정방향 참조 필드' on 'r on m.r_id = r.id'
where 조건절;

select * from '역방향 참조 필드' where id in ('첫번째 쿼리결과 id 리스트');

- queryset 캐시를 재활용하지 못하는 querset 호출

 

.all()로 질의하면 result_cache를 재사용한다.

.filter()를 사용하면 cache를 재사용하지 않고 또 sql문이 발생한다.


#회사의 목록을 EagerLoading함
company_list = list(Company.objects.prefetch_related("product_set").all()) 

#아래 두개는 위에서 쿼리가 돌았기때문에 SQL을 호출하지 않는다.
company = company_list[0]
company.product_set.all()#여기까지 EagerLoading해서 SQL 발생 XXXX 

company.product_set.filter(name="불닭볶음면")#여기서는 SQL발생한다. 

#SQL을 발생시키지 않으려면 all을 사용해서 찾는 방식을 사용해야 한다.
fire_noodel_project_list = [product for product in company.prodect_set.all() if product.name="불닭볶음면"]


- RawQuerySet은 NativeSQL이 아니다.

 

원하는 SQL을 위해 .raw()를 사용하자

raw_query = Model.objects.raw("select * from model where")# raw()를 선언하면 RawQuerySet

query = Model.objects.filter #raw() 이외에는 전부 QuerySet을 반환한다.

.raw()를 사용하게 되면 아래와 같은 메서드들을 사용할 수 없다. 

이것들을 전부 raw()에서 NativeSQL로 작성해 줘야한다.

.select_related()

FilteredRelation()

.annotate()

.order_by()

.extra()

[:10],,,[:2] 메인 쿼리에 limit 옵션을 걸 수 없다.

 

- 서브쿼리 발생 조건 : QuerySet In QuerySet

아직 company_queryset이 수행되기 전이기 때문에

서브쿼리로 발생하게 된다.


#company pk가 20이하인 Product들을 전부 조회
company_queryset = Company.objects.filter(id__lte=20).value_list("id", flat=True)
#서브쿼리로 발생하게 된다.
product_queryset = Product.objects.filter(product_owned_company__id_in = company_queryset)


서브 쿼리를 막기 위해서는

#list로 묶어서 바로 queryset을 수행시킨다.
company_queryset = list(Company.objects.filter(id__lte=20).value_list("id", flat=True))

product_queryset = Product.objects.filter(product_owned_company__id_in = company_queryset)

- values() values_list() 사용시 주의점

values() values_list()를 사용하게 되면 select_related(), prefetch_related() 옵션을 전부 무시한다.

values() values_list()를 사용하게 되면 DB Raw단위로 데이터를 반환한다.

즉 객체(object)와 관계지향(relational) 간에 매핑(mapping)이 일어나지 않는다.

 

 

 

반응형