关于django的prefetch_related优化查询问题

prefetch_related 介绍参见 https://blog.csdn.net/pushiqiang/article/details/79560550

在使用中发现,在对内嵌子列表再次进行filter时,prefetch_related会失效

models.py定义如下:

class Category(Model):
    """
    种类
    """
    name = models.CharField(_(u'name'), max_length=100, db_index=True)

class Book(Model):
    name = models.CharField(_(u'name'), max_length=100, db_index=True)
    is_published = models.BooleanField(_(u'is published'), default=False)
    category = models.ForeignKey(Category,
                                 verbose_name=_(u'Book category'),
                                 related_name='books')
# https://docs.djangoproject.com/en/dev/ref/models/querysets/#prefetch-related
# 查询种类列表的同时预加载内嵌的Book列表
categories = Category.objects.prefetch_related(
            Prefetch(
                'books',
                queryset=Book.objects.all()))

在使用django-extensions的shell环境下测试(使用 python manage.py shell_plus --print-sql 进入django的shell环境)

正确使用的情况

在遍历种类列表categories时打印books列表,会触发两条sql语句,prefetch_related正常起作用

for category in categories:
    print category.books.all()
In [39]: for category in categories:
    ...:     print category.books.all()
    ...:     
SELECT "category"."id",
       "category"."name",
FROM "category"
Execution time: 0.000725s [Database: slave]

SELECT "book"."id",
       "book"."name",
FROM "book"
WHERE "book"."category_id" IN (1, 2, 3)
Execution time: 0.000689s [Database: default]

[<Book: 语文>, <Book: 数学>]
[<Book: 大数据>, <Book: 历史>]

失效的情况

若是再次在books变量上进行filter,则prefetch-related会失效,会在遍历categories时对每个分类的books进行一次查询,即如果有100个分类,会进行100次books列表查询

for category in categories:
    print category.books.filter(is_published=True)
In [40]: for category in categories:
    ...:     print category.books.filter(is_published=True)    
    ...:     
SELECT "book"."id",
       "book"."is_published",
FROM "book"
WHERE ("book"."category_id" = 1
       AND "book"."is_published" = true)
LIMIT 21

Execution time: 0.000805s [Database: default]

[]
SELECT "book"."id",
       "book"."is_published",
FROM "book"
WHERE ("book"."category_id" = 2
       AND "book"."is_recommended" = true)
LIMIT 21

Execution time: 0.001096s [Database: default]

[]
SELECT "book"."id",
       "book"."is_published",
FROM "book"
WHERE ("book"."category_id" = 3
       AND "book"."is_recommended" = true)
LIMIT 21

Execution time: 0.001096s [Database: default]

总结

因此,在使用prefetch-related进行优化查询时,内嵌子列表的过滤必须在prefetch-related函数调用时指定过滤条件,而不能在遍历时再次对子列表进行过滤查询,最终的外键反向查询结果会从prefetch-related结果中提取

categories = Category.objects.prefetch_related(
            Prefetch(
                'books',
                queryset=Book.objects.filter(is_recommended=True)))

猜你喜欢

转载自blog.csdn.net/pushiqiang/article/details/80678192