Django의 select 인스턴스 자세히 보기related 및 prefetchrelated 함수가QuerySet 조회에 대한 최적화 (2)

11057 단어 독학의 길
이 시리즈의 두 번째 편입니다. 내용은prefetch 입니다.related () 함수의 용도, 실현 경로, 사용 방법
이 시리즈의 1편은 여기 있습니다.
3편 여기 있어요.
3.prefetch_related()
prefetchrelated ()로 최적화합니다.혹시 One Tomany Field라는 게 하나도 없다고 말할지도 몰라요.실제로 ForeignKey는 다대일 필드이고 ForeignKey와 관련된 필드는 다대일 필드입니다.
3.1 역할 및 방법
prefetch_related() 및 selectrelated()의 디자인 목적은 매우 비슷하다. 모두 SQL 조회의 수를 줄이기 위한 것이지만 실현 방식은 다르다.후자는 JOIN 문을 통해 SQL 쿼리 내에서 문제를 해결합니다.그러나 다대다 관계에 대해 SQL 문구를 사용하여 해결하는 것은 현명하지 않다. JOIN이 얻은 표가 길기 때문에 SQL 문구의 운행 시간이 증가하고 메모리가 차지하는 시간이 증가하기 때문이다.n개의 객체가 있는 경우 각 객체의 여러 쌍 다중 필드가 Mi 막대에 해당합니다.Σ(n) Mi 행의 결과 테이블.
prefetch_related () 의 해결 방법은 각각의 표를 조회한 다음 파이톤으로 그들 사이의 관계를 처리하는 것이다.계속해서 상기 예시를 설명하자면, 만약에 우리가 장삼이 가 본 모든 도시를 얻으려면prefetchrelated () 는 다음과 같이 해야 합니다.
>>> zhangs = Person.objects.prefetch_related('visitation').get(firstname=u" ",lastname=u" ")  
>>> for city in zhangs.visitation.all() :  
...   print city  
...  

위 코드에서 트리거한 SQL 쿼리는 다음과 같습니다.
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,  
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`   
FROM `QSOptimize_person`   
WHERE (`QSOptimize_person`.`lastname` = ' '  AND `QSOptimize_person`.`firstname` = ' ');   
  
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,   
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`   
FROM `QSOptimize_city`   
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)  
WHERE `QSOptimize_person_visitation`.`person_id` IN (1);  

첫 번째 SQL 조회는 장삼의Person 대상을 얻는 것일 뿐, 두 번째 관건은 관계표'QSoptimizeperson_visitation`중`personid`는 장삼의 줄로 하고'city`표내연(INNNER JOIN도 등치연결이라고도 부른다)과 결과표를 얻는다.
+----+-----------+----------+-------------+-----------+  
| id | firstname | lastname | hometown_id | living_id |  
+----+-----------+----------+-------------+-----------+  
|  1 |          |         |           3 |         1 |  
+----+-----------+----------+-------------+-----------+  
1 row in set (0.00 sec)  
  
+-----------------------+----+-----------+-------------+  
| _prefetch_related_val | id | name      | province_id |  
+-----------------------+----+-----------+-------------+  
|                     1 |  1 |        |           1 |  
|                     1 |  2 |        |           2 |  
|                     1 |  3 |        |           1 |  
+-----------------------+----+-----------+-------------+  
3 rows in set (0.00 sec)  

분명히 장삼무한, 광저우, 십언은 모두 가 본 적이 있다.
또한, 우리는 호북성의 모든 도시 이름을 얻으려면 이렇게 할 수 있다.
>>> hb = Province.objects.prefetch_related('city_set').get(name__iexact=u"   ")  
>>> for city in hb.city_set.all():  
...   city.name  
...  

SQL 쿼리 트리거:
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`   
FROM `QSOptimize_province`   
WHERE `QSOptimize_province`.`name` LIKE '   ' ;  
  
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`   
FROM `QSOptimize_city`   
WHERE `QSOptimize_city`.`province_id` IN (1);  

획득한 표:
+----+-----------+  
| id | name      |  
+----+-----------+  
|  1 |        |  
+----+-----------+  
1 row in set (0.00 sec)  
  
+----+-----------+-------------+  
| id | name      | province_id |  
+----+-----------+-------------+  
|  1 |        |           1 |  
|  3 |        |           1 |  
+----+-----------+-------------+  
2 rows in set (0.00 sec) 

우리는 prefetch가 사용하는 것이 IN 문장으로 이루어진 것을 볼 수 있다.이렇게 하면QuerySet에 있는 대상의 수가 너무 많을 때 데이터베이스 특성에 따라 성능 문제가 발생할 수 있습니다.
3.2 사용 방법
*lookups 매개 변수
prefetch_related () 는 Django < 1.7 에서 이 방법만 사용합니다.및 selectrelated () 와 같이prefetchrelated () 도 깊이 있는 검색을 지원합니다. 예를 들어 장씨 성을 가진 모든 사람이 갔던 성을 얻으려면:
>>> zhangs = Person.objects.prefetch_related('visitation__province').filter(firstname__iexact=u' ')  
>>> for i in zhangs:  
...   for city in i.visitation.all():  
...     print city.province  
...  

트리거된 SQL:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,   
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`   
FROM `QSOptimize_person`   
WHERE `QSOptimize_person`.`firstname` LIKE ' ' ;  
  
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,  
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` FROM `QSOptimize_city`   
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)  
WHERE `QSOptimize_person_visitation`.`person_id` IN (1, 4);  
  
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`   
FROM `QSOptimize_province`   
WHERE `QSOptimize_province`.`id` IN (1, 2);

결과:
+----+-----------+----------+-------------+-----------+  
| id | firstname | lastname | hometown_id | living_id |  
+----+-----------+----------+-------------+-----------+  
|  1 |          |         |           3 |         1 |  
|  4 |          |         |           2 |         2 |  
+----+-----------+----------+-------------+-----------+  
2 rows in set (0.00 sec)  
  
+-----------------------+----+-----------+-------------+  
| _prefetch_related_val | id | name      | province_id |  
+-----------------------+----+-----------+-------------+  
|                     1 |  1 |        |           1 |  
|                     1 |  2 |        |           2 |  
|                     4 |  2 |        |           2 |  
|                     1 |  3 |        |           1 |  
+-----------------------+----+-----------+-------------+  
4 rows in set (0.00 sec)  
  
+----+-----------+  
| id | name      |  
+----+-----------+  
|  1 |        |  
|  2 |        |  
+----+-----------+  
2 rows in set (0.00 sec)

특히 체인식 prefetchrelated는 1.7의 select 처럼 이 검색을 추가합니다.related처럼.
주의해야 할 것은QuerySet을 사용할 때 체인 작업에서 데이터베이스 요청이 바뀌면 이전에prefetchrelated 캐시된 데이터는 무시됩니다.이로 인해 Django는 데이터베이스에 해당하는 데이터를 요청하여 성능 문제를 일으킬 수 있습니다.여기서 언급한 데이터베이스 변경 요청은 각종 Filter(), exclude() 등이 최종적으로 SQL 코드를 바꾸는 작업을 가리킨다.all()는 최종 데이터베이스 요청을 바꾸지 않기 때문에 데이터베이스를 다시 요청하지 않습니다.
예를 들어 모든 사람이 방문한 도시에'시'자가 있는 도시를 얻으려면 대량의 SQL 조회를 초래할 수 있다.
plist = Person.objects.prefetch_related('visitation')  
[p.visitation.filter(name__icontains=u" ") for p in plist]  

데이터베이스에 4명이 있으므로 SQL 쿼리 2+4회:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`,   
`QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`   
FROM `QSOptimize_person`;  
  
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,  
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`   
FROM `QSOptimize_city`   
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)  
WHERE `QSOptimize_person_visitation`.`person_id` IN (1, 2, 3, 4);  
  
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`   
FROM `QSOptimize_city`   
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)   
WHERE(`QSOptimize_person_visitation`.`person_id` = 1  AND `QSOptimize_city`.`name` LIKE '% %' );  
  
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`   
FROM `QSOptimize_city`   
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)   
WHERE (`QSOptimize_person_visitation`.`person_id` = 2  AND `QSOptimize_city`.`name` LIKE '% %' );   
  
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`   
FROM `QSOptimize_city`  
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)   
WHERE (`QSOptimize_person_visitation`.`person_id` = 3  AND `QSOptimize_city`.`name` LIKE '% %' );  
  
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`   
FROM `QSOptimize_city`   
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)   
WHERE (`QSOptimize_person_visitation`.`person_id` = 4  AND `QSOptimize_city`.`name` LIKE '% %' );  

이 요청 사건들을 상세하게 분석해 보세요.
모두가 알다시피,QuerySet은 lazy의 것으로, 사용할 때만 데이터베이스에 접근할 수 있다.두 번째 줄의 Python 코드를 실행할 때 for 순환은plist를iterator로 간주하고 데이터베이스 조회를 촉발합니다.최초의 두 번의 SQL 조회는prefetchrelated로 인한.
검색 결과에 필요한 모든 시티 정보가 포함되어 있지만, 순환체에서 Person에 대한 정보가 포함되어 있기 때문입니다.visitation에서 Filter 작업을 진행했는데 데이터베이스 요청이 바뀌었습니다.따라서 이러한 작업은 이전에 캐시된 데이터를 무시하고 SQL 조회를 다시 수행합니다.
그런데 이런 수요가 생기면 어떡하지?Django>=1.7에서는 다음 섹션의 Prefetch 대상을 통해 실현할 수 있으며, 만약 환경이 Django<1.7이라면 Python에서 이 부분을 완성할 수 있습니다.
plist = Person.objects.prefetch_related('visitation')  
[[city for city in p.visitation.all() if u" " in city.name] for p in plist]  

3.3 Prefetch 객체
Django>=1.7에서prefetch 대상을 사용하여prefetch 를 제어할 수 있습니다related 함수의 행동입니다.
Prefetch 객체의 피쳐:
하나의 Prefetch 객체는 하나의 prefetch 작업만 지정할 수 있습니다
Prefetch 객체가 필드에 지정하는 방법 및 prefetchrelated의 매개 변수는 쌍밑줄로 연결된 필드 이름으로 이루어집니다
queryset 매개 변수를 통해prefetch에서 사용하는QuerySet을 수동으로 지정할 수 있습니다
toattr 매개 변수는 prefetch에서 오는 속성 이름을 지정합니다
Prefetch 객체와 문자열 형식으로 지정된 lookups 매개 변수를 혼용할 수 있습니다
계속해서 위의 예를 들어 모든 사람들이 방문한 도시에'무'자와'주'가 있는 도시를 얻는다.
wus = City.objects.filter(name__icontains = u" ")  
zhous = City.objects.filter(name__icontains = u" ")  
plist = Person.objects.prefetch_related(  
    Prefetch('visitation', queryset = wus, to_attr = "wu_city"),  
    Prefetch('visitation', queryset = zhous, to_attr = "zhou_city"),)  
[p.wu_city for p in plist]  
[p.zhou_city for p in plist] 

주: 이 코드는 실제 환경에서 테스트한 적이 없습니다. 정확하지 않은 부분이 있으면 바로잡아 주십시오.
또한 Prefetch 객체와 문자열 매개변수를 혼용할 수 있습니다.
3.4None
None에 전송하여 이전 prefetch를 비울 수 있습니다related.다음과 같이 하십시오.
>>> prefetch_cleared_qset = qset.prefetch_related(None)  

3.5 매듭
prefetch_related는 주로 일대다와 다대다의 관계를 최적화시킨다
prefetch_related는 각 테이블의 내용을 각각 얻은 다음에 파이톤으로 그들 사이의 관계를 처리하여 최적화시킨다
가변 길이 매개 변수를 통해 select 필요를 지정할 수 있습니다related의 필드 이름입니다.지정 방식과 특징 및 selectrelated는 동일합니다.
Django>=1.7에서는 Prefetch 대상을 통해 복잡한 조회를 할 수 있지만 낮은 버전의 Django는 스스로 할 수 있을 것 같다
prefetch로related 매개 변수, Prefetch 대상과 문자열을 혼용할 수 있습니다
prefetch_related의 체인 호출은 대응하는prefetch를 추가하는 것이지 교체하는 것이 아닙니다. 서로 다른 버전에 근거한 차이가 없는 것 같습니다
None에 전송하여 이전 prefetch를 비울 수 있습니다related.
  view plain
 co

좋은 웹페이지 즐겨찾기