users = User.objects.all()
for user in users:
user.userinfo
user_list = list(users)
위의 예시는 서버에서 ORM을 이해하지 못하고 사용했을 때 발생하는 문제이다
위의 로그를 보면 알겠지만... for문을 돌면서 처음 users인 모든 User를 불러는 SQL 하나와, user.userinfo를 부를 때 마다 인스턴스 하나 하나 쿼리문으로 불러낸다.
예를들어 10명의 User가 있다면 10명의 User를 부르는 SQL 하나와 user1부터 user2 ... user10까지 각자 단일 쿼리문을 사용해서 불러낸다.
개발자라면 위의 코드를 보고 전체 QuerySet을 불러오는 SQL 하나만 있으면 더 이상의 SQL은 필요없겠구나 라고 생각할 것이다.
하지만 ORM은 아니다
전체 QuerySet을 불러오는 SQL 하나 + User의 총 명수대로 SQL을 실행한다.
즉, 1번 실행할 것을 N + 1 번 실행한다.
이것을 N + 1 Problem 이라고 한다.
QuerySet은 1개의 쿼리와 0~N개의 추가쿼리(셋)로 구성되어 있다
Model.objects
.filter(조건절)
.select_related('정방향_참조_필드')
.prefetch_related('역방향_참조_필드')
select * from 'Model' as m
(inner OR left outer) join '정방향_참조_필드' as r
on m.r_id=r.id
where '조건절';
select * from '역방향_참조_필드' where id in ('첫번째 쿼리 결과의 id 리스트');
prefetch는 추가 쿼리를 발생시킴. 만약 prefetch 뒤에 filter가 이루어지면 근데 거기에 filter의 조건으로 prefetch를 해서 나온 결과의 field라면??
prefetch는 추가 쿼리 select는 join이다. 위의 경우에 prefetch를 해서 추가 쿼리를 발생시켜야 하는데, filter 때문에 위에서 먼저 쓸데없이 join을 하고 추가 쿼리를 발생시켜서 비효율적으로 된다.
그래서 prefetch 같은 경우는 filter 아래에 두고 사용하도록 하자!
https://kimdoky.github.io/django/2020/03/11/django-querysets-cashing-eval/