เหตุใด prefetch_related () ของ django จึงใช้ได้เฉพาะกับ all () และไม่กรอง ()


90

สมมติว่าฉันมีโมเดลนี้:

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

สิ่งนี้ทำเพียงสองแบบสอบถามเท่านั้นซึ่งเป็นสิ่งที่ฉันคาดหวัง (รายการหนึ่งจะได้รับอัลบั้มจากนั้นหนึ่งเช่น `` เลือก * ในรูปภาพ 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! ฉันทำอะไรผิดหรือ django ไม่ฉลาดพอที่จะรู้ว่ามันดึงรูปภาพทั้งหมดมาแล้วและสามารถกรองเป็น python ได้หรือไม่? ฉันสาบานว่าฉันอ่านที่ไหนสักแห่งในเอกสารว่าควรจะทำอย่างนั้น ...


คำตอบ:


168

ใน 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

จากเอกสาร :

... เช่นเคยกับ QuerySets วิธีการผูกมัดที่ตามมาซึ่งบ่งบอกถึงการสืบค้นฐานข้อมูลอื่นจะละเว้นผลลัพธ์ที่แคชไว้ก่อนหน้านี้และดึงข้อมูลโดยใช้แบบสอบถามฐานข้อมูลใหม่ ดังนั้นหากคุณเขียนสิ่งต่อไปนี้:

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.