django의 prefetch_related ()가 all ()에서만 작동하고 filter ()가 아닌 이유는 무엇입니까?


89

이 모델이 있다고 가정합니다.

class PhotoAlbum(models.Model):
    title = models.CharField(max_length=128)
    author = models.CharField(max_length=128)

class Photo(models.Model):
    album = models.ForeignKey('PhotoAlbum')
    format = models.IntegerField()

이제 앨범의 하위 집합에있는 사진의 하위 집합을 효율적으로보고 싶다면. 나는 다음과 같이 그것을한다.

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set")
for a in someAlbums:
    somePhotos = a.photo_set.all()

이것은 내가 기대하는 두 개의 쿼리 만 수행합니다 (하나는 앨범을 가져오고 하나는`SELECT * IN photos WHERE photoalbum_id IN ()).

모든것이 좋아.

하지만 이렇게하면 :

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set")
for a in someAlbums:
    somePhotos = a.photo_set.filter(format=1)

그런 다음 WHERE format = 1!로 수많은 쿼리를 수행합니다 . 내가 뭘 잘못하고 있거나 장고가 이미 모든 사진을 가져 왔고 파이썬으로 필터링 할 수 있다는 것을 깨달을만큼 똑똑하지 않은가? 나는 문서의 어딘가에서 읽었을 것이라고 맹세합니다 ...


답변:


167

Django 1.6 및 이전 버전에서는 추가 쿼리를 피할 수 없습니다. 이 prefetch_related호출 a.photoset.all()은 쿼리 세트의 모든 앨범에 대한 결과를 효과적으로 캐시합니다 . 그러나, a.photoset.filter(format=1)당신이 모든 앨범에 대한 추가 쿼리를 생성 할 수 있도록 다른 검색어 세트이다.

이것은 prefetch_related문서에 설명되어 있습니다. 은 filter(format=1)동일합니다 filter(spicy=True).

대신 Python에서 사진을 필터링하여 수 또는 쿼리를 줄일 수 있습니다.

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related("photo_set")
for a in someAlbums:
    somePhotos = [p for p in a.photo_set.all() if p.format == 1]

Django 1.7에는 Prefetch()의 동작을 제어 할 수 있는 객체가 있습니다 prefetch_related.

from django.db.models import Prefetch

someAlbums = PhotoAlbum.objects.filter(author="Davey Jones").prefetch_related(
    Prefetch(
        "photo_set",
        queryset=Photo.objects.filter(format=1),
        to_attr="some_photos"
    )
)
for a in someAlbums:
    somePhotos = a.some_photos

Prefetch객체 사용 방법에 대한 더 많은 예 는 prefetch_related문서를 참조하세요 .


8

로부터 문서 :

... 항상 QuerySet과 마찬가지로, 다른 데이터베이스 쿼리를 암시하는 후속 연결 메서드는 이전에 캐시 된 결과를 무시하고 새로운 데이터베이스 쿼리를 사용하여 데이터를 검색합니다. 따라서 다음과 같이 작성하면 :

pizzas = Pizza.objects.prefetch_related('toppings') [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]

... 그런 다음 pizza.toppings.all ()이 프리 페치되었다는 사실은 도움이되지 않습니다. 실제로 사용하지 않은 데이터베이스 쿼리를 수행했기 때문에 성능이 저하됩니다. 따라서이 기능을주의해서 사용하십시오!

귀하의 경우 "a.photo_set.filter (format = 1)"은 새로운 쿼리처럼 취급됩니다.

또한 "photo_set"은 다른 관리자를 통해 모두 구현 된 역방향 조회입니다.


photo_set을 사용하여 프리 페치 할 수도 있습니다 .prefetch_related('photo_set'). 그러나 당신이 설명했듯이 순서는 중요합니다.
Risadinha

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.