Tại sao prefetch_osystem () của django chỉ hoạt động với all () chứ không phải filter ()?


89

giả sử tôi có mô hình này:

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()

Bây giờ nếu tôi muốn xem một tập hợp con các bức ảnh trong một tập hợp con các album một cách hiệu quả. Tôi làm điều đó một cái gì đó như thế này:

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

Điều này chỉ thực hiện hai truy vấn, đó là những gì tôi mong đợi (một để lấy các album và sau đó một truy vấn như `CHỌN * TRONG ảnh WHERE photoalbum_id IN ().

Mọi thứ đều tuyệt vời.

Nhưng nếu tôi làm điều này:

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

Sau đó, nó thực hiện rất nhiều truy vấn với WHERE format = 1! Tôi đang làm gì đó sai hay django không đủ thông minh để nhận ra rằng nó đã tìm nạp tất cả các bức ảnh và có thể lọc chúng trong python? Tôi thề là tôi đã đọc ở đâu đó trong tài liệu rằng nó phải làm điều đó ...


có thể trùng lặp của Lọc trên prefetch_related trong Django
akaihola

Câu trả lời:


166

Trong Django 1.6 trở về trước, không thể tránh được các truy vấn bổ sung. Cuộc prefetch_relatedgọi lưu trữ hiệu quả các kết quả của a.photoset.all()mọi anbom trong bộ truy vấn. Tuy nhiên, a.photoset.filter(format=1)là một bộ truy vấn khác, vì vậy bạn sẽ tạo thêm một truy vấn cho mỗi album.

Điều này được giải thích trong prefetch_relatedtài liệu. Các filter(format=1)tương đương với filter(spicy=True).

Lưu ý rằng thay vào đó bạn có thể giảm số lượng hoặc truy vấn bằng cách lọc ảnh trong 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]

Trong Django 1.7, có một Prefetch()đối tượng cho phép bạn kiểm soát hành vi của 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

Để biết thêm ví dụ về cách sử dụng Prefetchđối tượng, hãy xem prefetch_relatedtài liệu.


8

Từ các tài liệu :

... như mọi khi với QuerySets, bất kỳ phương thức chuỗi nào tiếp theo ngụ ý một truy vấn cơ sở dữ liệu khác sẽ bỏ qua các kết quả đã lưu trong bộ nhớ cache trước đó và truy xuất dữ liệu bằng truy vấn cơ sở dữ liệu mới. Vì vậy, nếu bạn viết như sau:

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

... thì thực tế là pizza.toppings.all () đã được tìm nạp trước sẽ không giúp ích được gì cho bạn - trên thực tế, nó ảnh hưởng đến hiệu suất, vì bạn đã thực hiện một truy vấn cơ sở dữ liệu mà bạn chưa sử dụng. Vì vậy, hãy sử dụng tính năng này một cách thận trọng!

Trong trường hợp của bạn, "a.photo_set.filter (format = 1)" được coi như một truy vấn mới.

Ngoài ra, "photo_set" là một tra cứu ngược - được thực hiện hoàn toàn thông qua một trình quản lý khác.


photo_setcũng có thể được tìm nạp trước với .prefetch_related('photo_set'). Nhưng thứ tự quan trọng, như bạn đã giải thích.
Risadinha

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.