prefetch_related

2018-06-06  本文已影响0人  lxy_悦

环境:

pip install django==1.8.3
pip install django-debug-toolbar==1.9.1

mysql版本:5.7.22

models.py

# -*- coding: utf-8 -*-
from django.db import models


class Province(models.Model):
    name = models.CharField(max_length=10)

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'Province'


class City(models.Model):
    name = models.CharField(max_length=5)
    province = models.ForeignKey(Province, related_name="city_set")

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'City'


class Person(models.Model):
    firstname = models.CharField(max_length=10)
    lastname = models.CharField(max_length=10)
    visitation = models.ManyToManyField(City, related_name="visitor")
    hometown = models.ForeignKey(City, related_name="birth")
    living = models.ForeignKey(City, related_name="citizen")

    def __str__(self):
        return self.firstname + self.lastname

    class Meta:
        db_table = 'Person'

创建表及插入数据
百度云盘mysql文件下载地址:https://pan.baidu.com/s/1Jv8dYU98b_667b8NvNKnNg

python manage.py makemigrations
python manage.py migrate

测试数据如上`mysql文件下载`, 在mysql中执行导入命令:
source test.sql;

如果导入过程报错,可能有外键约束,在mysql中执行:
set foreign_key_checks = 0;

也可自行创建数据。django插入示例如下:
obj = Person.objects.create(
    firstname='王',
    lastname='五',
    hometown_id=2,
    living_id=5
)
obj.save()
obj.visitation.add(c_objs[0])
obj.visitation.add(c_objs[3])

测试

>>> from mytest_app.models import *

# 获取张三游览过的城市
>>> Person.objects.prefetch_related('visitation').get(firstname=u"张", lastname=u"三").visitation.all()   # 获 取张三游览过的城市
SELECT `Person`.`id`,
       `Person`.`firstname`,
       `Person`.`lastname`,
       `Person`.`hometown_id`,
       `Person`.`living_id`
FROM `Person`
WHERE (`Person`.`lastname` = '三'
       AND `Person`.`firstname` = '张') [1.46ms]
SELECT (`Person_visitation`.`person_id`) AS `_prefetch_related_val_person_id`,
       `City`.`id`,
       `City`.`name`,
       `City`.`province_id`
FROM `City`
INNER JOIN `Person_visitation` ON (`City`.`id` = `Person_visitation`.`city_id`)
WHERE `Person_visitation`.`person_id` IN (3) [0.48ms]
[<City: 武汉>, <City: 咸宁>, <City: 上海>]

如上,会生成2条sql, 下面再看正向查询:
>>> City.objects.filter(visitor__firstname=u'张', visitor__lastname=u'三')
SELECT `City`.`id`,
       `City`.`name`,
       `City`.`province_id`
FROM `City`
INNER JOIN `Person_visitation` ON (`City`.`id` = `Person_visitation`.`city_id`)
INNER JOIN `Person` ON (`Person_visitation`.`person_id` = `Person`.`id`)
WHERE (`Person`.`lastname` = '三'
       AND `Person`.`firstname` = '张')
LIMIT 21 [1.60ms]
[<City: 武汉>, <City: 咸宁>, <City: 上海>]


正向查询只需要一条sql,但是当表数据很多的时候,会出现性能问题


再来看看 select_related 和 prefetch_related 对比:

# 获得所有家乡是湖北的人

# prefetch_related 查询
>>> hb = Province.objects.prefetch_related("city_set__birth").get(name__iexact=u"湖北")
SELECT `Province`.`id`,
       `Province`.`name`
FROM `Province`
WHERE `Province`.`name` LIKE '湖北' [0.68ms]
SELECT `City`.`id`,
       `City`.`name`,
       `City`.`province_id`
FROM `City`
WHERE `City`.`province_id` IN (1) [0.31ms]
SELECT `Person`.`id`,
       `Person`.`firstname`,
       `Person`.`lastname`,
       `Person`.`hometown_id`,
       `Person`.`living_id`
FROM `Person`
WHERE `Person`.`hometown_id` IN (1,
                                 2,
                                 3) [0.40ms]
>>> people = []
>>> for city in hb.city_set.all():
...     people.extend(city.birth.all())
...
>>> print(people)
[<Person: 李悦>, <Person: 李飞>, <Person: 张三>, <Person: 王五>, <Person: 李四>]


# select_related 查询
>>> Person.objects.select_related("hometown__province").filter(hometown__province__name__iexact=u"湖北")
SELECT `Person`.`id`,
       `Person`.`firstname`,
       `Person`.`lastname`,
       `Person`.`hometown_id`,
       `Person`.`living_id`,
       `City`.`id`,
       `City`.`name`,
       `City`.`province_id`,
       `Province`.`id`,
       `Province`.`name`
FROM `Person`
INNER JOIN `City` ON (`Person`.`hometown_id` = `City`.`id`)
INNER JOIN `Province` ON (`City`.`province_id` = `Province`.`id`)
WHERE `Province`.`name` LIKE '湖北'
LIMIT 21 [0.38ms]
[<Person: 李悦>, <Person: 李飞>, <Person: 张三>, <Person: 李四>, <Person: 王五>]


  1. select_related()的效率要高于prefetch_related()。因此,最好在能用select_related()的地方尽量使用它,也就是说,对于ForeignKey字段,避免使用prefetch_related()。
  2. select_related 只能用于一对一查询,不能用于多对多,而 prefetch_related 则都行,只是都是分别查询。
  3. prefetch_related 相当于语言层面的连接查询,写起来跟 select_related 一样,好像是做了表之间的连接,但是它实际上的解决方法是,分别查询每一个表,然后用Python处理他们之间的关系。
  4. 你可以在一个QuerySet中同时使用select_related()和prefetch_related(),从而减少SQL查询的次数。但是只有prefetch_related()之前的select_related()是有效的,之后的将会被无视掉。

相关参考文档:
实例详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(二)
用实例详解Python中的Django框架中prefetch_related()函数对数据库查询的优化
Django的select_related 和 prefetch_related 函数优化查询

上一篇下一篇

猜你喜欢

热点阅读