me` 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查询就是prefetch_related导致的。
虽然已经查询结果中包含所有所需的city的信息,但因为在循环体中对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]
Prefetch 对象
在Django >= 1.7,可以用Prefetch对象来控制prefetch_related函数的行为。
注:由于我没有安装1.7版本的Django环境,本节内容是参考Django文档写的,没有进行实际的测试。
Prefetch对象的特征:
一个Prefetch对象只能指定一项prefetch操作。 Prefetch对象对字段指定的方式和prefetch_related中的参数相同,都是通过双下划线连接的字段名完成的。 可以通过 queryset 参数手动指定prefetch使用的QuerySet。 可以通过 to_attr 参数指定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对象和字符串参数可以混用。
None
可以通过传入一个None来清空之前的prefetch_related。就像这样:
>>> prefetch_cleared_qset = qset.prefetch_related(None)
小结
prefetch_related主要针一对多和多对多关系进行优化。prefetch_related通过分别获取各个表的内容,然后用Python处理他们之间的关系来进行优化。可以通过可变长参数指定需要select_related的字段名。指定方式和特征与select_related是相同的。在Django >= 1.7可以通过Prefetch对象来实现复杂查询,但低版本的Django好像只能自己实现。作为prefetch_related的参数,Prefetch对象和字符串可以混用。prefetch_related的链式调用会将对应的prefetch添加进去,而非替换,似乎没有基于不同版本上区别。可以通过传入None来清空之前的prefetch_related。