QuerySet을 통해 알아보는 ORM의 특징
- Lazy Loading : 쿼리셋을 선언할 때는 쿼리셋 자체로만 존재한다. 실제 SQL이 실행되는 시점은, 그 쿼리셋이 사용되었을 때이다. 당장 필요하지 않으면 호출을 지연하는 특성. 꼭 필요한 시점에만 필요한 만큼 SQL이 호출된다. 재사용하지 못함으로, 불필요한 쿼리가 더 호출될 수 있다.
- Caching : QuerySet 캐싱을 재사용할 수 있음. 모든 user를 먼저 캐싱해두고, 필요한 특정 유저 정보를 가져올 때 앞의 변수를 활용하면 추가 쿼리를 호출하지 않는다.
- Eager Loading(즉시로딩, N+1 Problem) : for문이 돌때마다 쿼리를 계속 돌린다. user가 100명 있으면 for문으로 userinfo를 얻기 위해서는 총 100+1번 쿼리가 호출되는 현상.
QuerySet 상세
- filter() : where절
- select_related() : JOIN
- prefetch_related() : 새로운 추가 쿼리셋
- CaptureQueriesContext : SQL Testcase 작성을 위한 유틸, 특정 쿼리셋에서 발생하는 쿼리의 갯수를 capture 할 수 있다.
실수하기 쉬운 QuerySet의 특성들
- prefetch_related()와 filter()는 완전 별개이다. filter의 조건절은 where절을 붙히기 위해 메인쿼리에서 INNER JOIN을 사용하게 되고, 추가 쿼리에서 한번 더 조회하므로 비효율적이다.
- 해결방안 : filter에 넣었던 product 관련 조건절을 Prefetch()에 제공 → 추가쿼리에 where 문이 수정됨
company_queryset = (Company.objects.prefetched_related('product_set').filter(name="company_name", product_name_isnull = False))
cs
- 추천하는 QuserySet의 순서 : annotate → selected_related → filter → only → prefetch_related (실제로 발생하는 SQL의 순서와 같음)
- QuerySet 캐시를 재활용하지 못하는 queryset의 호출
# 회사의 상품목록을 eager loading 함company_list = list(Company.objects.prefetch_related('product_set').all()))# company_list의 result_cache를 재활용하므로 쿼리 발생 Xcompany = company_list[0]# filter()를 사용하면 result_cache를 재활용 X 쿼리를 수행 Ocompany_list.filter(name="불닭볶음면")# .all()로 질의하면 result_cache를 재활용하므로 쿼리 발생 Xfire_noodle_product_list = [product for product in company.product_set.all() if product_name="불닭볶음면"]cs
- 서브쿼리 발생조건(Queryset in Queryset) : 일반적으로 서브쿼리는 slow query를 유도하기 때문에 지양하지만, 개발자의 의도와 다르게 쿼리셋을 사용하게 되는 경우가 있음.
company_queryset : QuerySet = Company.objects.filter(id=20).values_list("id", flat=True)# company_queryset 로직이 여기서 수행되므로 의도치않은 서브쿼리 발생product_queryset : QuerySet = Product.pbjects.filter(company_id__in=company_queryset)# 해결방안 : list로 묶어서 queryset을 바로 수행해버리자.company_queryset : List[Company] = list(Company.objects.filter(id=20))# 같은 조건이라도 filter() 내에서는 JOIN으로 수행되지만,# 역방향참조모델의 exclude() 또는 filter내 ~Q()로 사용시 무조건, 서브쿼리가 발생.# 차선책 : prefetch_related(Prefetch()), 정방향참조모델에서는 JOIN으로 수행된다.
cs
- 쿼리셋의 다양한 리턴 타입 : values(), values_list()
result : List[Model] = Model.objects.all()# .only() : 지정한 필드만 조회# .defer() : 지정한 필드만 제외하고 조회# ValuesIterableresult : List[Dict[str, Any]] = Model.objects.values()# ValuesListIterableresult : List[Tuple[str, Any]] = Model.objects.values_list()# FlatValuesListIterableresult : List[Any] = Model.objects.values_list('pk', flat=True)# FlatValuesListIterable, django에서 제공하는 Raw 객체에 담아서 리턴result : List[Raw] = Model.objects.values_list(named=True)
cs
- values(), values_list() 사용시 DB Raw 단위 데이터를 반환하기 때문에 select_related()와 prefetch_related()의 eagger loading 옵션이 무시된다.
https://www.youtube.com/watch?v=EZgLfDrUlrk&t=460s
https://github.com/KimSoungRyoul/Django_ORM_pratice_project/tree/master
업무하면서 답답한 부분이 많이 해소되었다. 파이콘 좋은 강의 감사드립니다.
'Programming > Python * Django' 카테고리의 다른 글
[Django] runserver 수행시 broken pipe 이슈 해결 (0) | 2021.07.05 |
---|---|
[Python] 정규표현식(Regular Expression) 기본 문법, 파이썬 re 모듈 활용 (0) | 2019.05.24 |
[Python] 파이썬 SQLite 연동하기 (0) | 2019.05.21 |
[Python] Selenium을 활용한 웹 크롤링 (0) | 2019.05.21 |