Django의 ORM은 다른 ORM
과 마찬가지로 Lazy-loading
방식을 사용한다. Lazy-loading
이란 ORM
에서 명령을 실행할 때마다 데이터베이스에 접근하여 데이터를 가져오는 것이 아닌 모든 명령처리가 끝나고 실제 데이터를 불러 와야할 때 데이터베이스 Query
문을 실행하는 방식
: 즉시로딩
Eager-loading ↔ Lazy-loading
Lazy-loading은 Query문을 하나하나 실행하여 데이터를 가져온다면
Earger-loading은 지금당장 사용하지 않을 데이터도 포함하여 Query 문을 실행 하기 때문에
Lazy-loading의 N+1문제의 해결책으로 많이 사용하게 된다.
Django에서 실행방법
select_related
는 각각의 lookup마다 SQL의JOIN
을 실행하여 테이블의 일부를 가져오고, select .. from
에서 관련된 필드들을 가져온다. 형식 parameter에는 참조하는 class 의 이름을 소문자로 쓰고 ‘ ’에 감싼다 여러개의 parameter를 가질수있다. 예) Store.objects.filter(id=1).select_related('gungu','city').values('id','name','city_name') prefetch_related :구하려는 객체가 정참조 multiple objects(many-to-many or one-to-many)이거나, 또는 역참조 Foreign Key일때 사용한다. selected_related
와 달리, prefetch_related
는 SQL의 JOIN
을 실행하지 않고, python에서 joining을 실행한다. 형식 <lowercase 모델이름>_set : 장고가 지어주는 default 역참조 메서드 이름 ForeignKey 필드에 이름을 정하면 default이름 대신 given 이름을 쓸 수 있다.Lazy-loading
의 성능이슈인 N+1 Query
문제는 외래키(Foreign Key
)를 참조해서 데이터를 가져올 때 발생한다.
아래와 같이 restaurant
와 owner
가 1:1 관계인 모델이 있다고 가정하자
mysql> select * from restaurants;
+----+--------------+--------------+----------+
| id | name | place | owner_id |
+----+--------------+--------------+----------+
| 1 | 얌얌피자 | 서울 | 1 |
| 2 | 굿굿피자 | 인천 | 2 |
| 3 | 좋아좋아 | 부산 | 3 |
| 4 | 배고파아 | 제주 | 4 |
| 5 | 정통스 | 이탈리아 | 5 |
+----+--------------+--------------+----------+
mysql> select * from owners;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 김 | 21 |
| 2 | 이 | 22 |
| 3 | 박 | 30 |
| 4 | 안 | 40 |
| 5 | 전 | 50 |
+----+------+-----+
>>> restaurants = Restaurant.objects.all()
>>>
>>> for restaurant in restaurants:
... restaurant.owner.name
이때 쿼리를 확인해보면 5개가 아닌 5+1인 6개의 쿼리가 날아간것을 확인할수있다.
{'sql': 'SELECT `restaurants`.`id`, `restaurants`.`name`, `restaurants`.`owner_id`, `restaurants`.`place` FROM `restaurants`', 'time': '0.003'},
{'sql': 'SELECT `owners`.`id`, `owners`.`name`, `owners`.`age` FROM `owners` WHERE `owners`.`id` = 1 LIMIT 21', 'time': '0.004'},
{'sql': 'SELECT `owners`.`id`, `owners`.`name`, `owners`.`age` FROM `owners` WHERE `owners`.`id` = 2 LIMIT 21', 'time': '0.001'},
{'sql': 'SELECT `owners`.`id`, `owners`.`name`, `owners`.`age` FROM `owners` WHERE `owners`.`id` = 3 LIMIT 21', 'time': '0.001'},
{'sql': 'SELECT `owners`.`id`, `owners`.`name`, `owners`.`age` FROM `owners` WHERE `owners`.`id` = 4 LIMIT 21', 'time': '0.001'},
{'sql': 'SELECT `owners`.`id`, `owners`.`name`, `owners`.`age` FROM `owners` WHERE `owners`.`id` = 5 LIMIT 21', 'time': '0.001'}]
맨 첫번 째 Query
문은 전체 restaurant
를 가져오고 그뒤 owner
에서 5번 따로 가져오게 된다. 이는 가져오는 데이터가 많으면 많을수록 비효율적인 코드가 되기 때문에 위에서 언급한 Eager-loading
을 통해 해결해야 한다.
쏘 디피컬트