美文网首页
prefetch_related

prefetch_related

作者: lxy_悦 | 来源:发表于2018-06-06 10:50 被阅读0次

    环境:

    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 函数优化查询

    相关文章

      网友评论

          本文标题:prefetch_related

          本文链接:https://www.haomeiwen.com/subject/zoxbsftx.html