Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化

引言

  在数据库存在外键的其情况下,使用select_related()和prefetch_related()很大程度上减少对数据库的请求次数以提高性能

1.实例准备

  模型:

  1. from django.db import models
  2.  
  3. # Create your models here.
  4. # 书
  5. class Book(models.Model):
  6. title = models.CharField(max_length=32)
  7. publish_date = models.DateField(auto_now_add=True)
  8. price = models.DecimalField(max_digits=5, decimal_places=2)
  9. memo = models.TextField(null=True)
  10. # 创建外键,关联publish
  11. publisher = models.ForeignKey(to="Publisher", on_delete=models.CASCADE, related_name='books')
  12. # 创建多对多关联author
  13. author = models.ManyToManyField(to="Author")
  14.  
  15. def __str__(self):
  16. return self.title
  17.  
  18. # 出版社
  19. class Publisher(models.Model):
  20. name = models.CharField(max_length=32)
  21. city = models.CharField(max_length=32)
  22.  
  23. def __str__(self):
  24. return self.name
  25.  
  26. # 作者
  27. class Author(models.Model):
  28. name = models.CharField(max_length=32)
  29. age = models.IntegerField()
  30. phone = models.CharField(max_length=11)
  31. detail = models.OneToOneField(to="AuthorDetail", on_delete=models.CASCADE)
  32.  
  33. def __str__(self):
  34. return self.name
  35.  
  36. # 作者详情
  37. class AuthorDetail(models.Model):
  38. addr = models.CharField(max_length=64)
  39. email = models.EmailField()

1.select_related()

  对于一对一字段(OneToOneField)和外键字段(ForeignKey)可以使用select_releated来优化QuerySet查询

示例:

  1)未调用该方法时,查询所有书籍以及所在的出版社

  1. book_obj = models.Book.objects.all()
  2. # 打印所有书籍以及所在的出版社
  3. for book in book_obj:
  4. print(book.publisher)

  输出的日志信息

  1. (0.000) SELECT @@SQL_AUTO_IS_NULL; args=None
  2. (0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
  3. (0.001) SELECT `app_book`.`id`, `app_book`.`title`, `app_book`.`publish_date`, `app_book`.`price`, `app_book`.`memo`, `app_book`.`publisher_id` FROM `app_book`; args=()
  4. 南方传媒出版社
  5. 长沙电视出版社
  6. 长沙电视出版社
  7. 清华大学出版社
  8. 人民邮电出版社
  9. (0.001) SELECT VERSION(); args=None
  10. 南方传媒出版社
  11. (0.001) SELECT `app_publisher`.`id`, `app_publisher`.`name`, `app_publisher`.`city` FROM `app_publisher` WHERE `app_publisher`.`id` = 1; args=(1,)
  12. (0.001) SELECT `app_publisher`.`id`, `app_publisher`.`name`, `app_publisher`.`city` FROM `app_publisher` WHERE `app_publisher`.`id` = 2; args=(2,)
  13. (0.000) SELECT `app_publisher`.`id`, `app_publisher`.`name`, `app_publisher`.`city` FROM `app_publisher` WHERE `app_publisher`.`id` = 2; args=(2,)
  14. (0.000) SELECT `app_publisher`.`id`, `app_publisher`.`name`, `app_publisher`.`city` FROM `app_publisher` WHERE `app_publisher`.`id` = 4; args=(4,)
  15. (0.001) SELECT `app_publisher`.`id`, `app_publisher`.`name`, `app_publisher`.`city` FROM `app_publisher` WHERE `app_publisher`.`id` = 3; args=(3,)
  16. (0.000) SELECT `app_publisher`.`id`, `app_publisher`.`name`, `app_publisher`.`city` FROM `app_publisher` WHERE `app_publisher`.`id` = 1; args=(1,)
  17.  
  18. Process finished with exit code 0

  2)调用该方法时,查询所有书籍以及所在的出版社

  1. book_obj = models.Book.objects.select_related().all()
  2. for book in book_obj:
  3. print(book.publisher)

  输出的日志信息  

  1. (0.001) SELECT @@SQL_AUTO_IS_NULL; args=None
  2. (0.001) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
  3. (0.001) SELECT `app_book`.`id`, `app_book`.`title`, `app_book`.`publish_date`,
    `app_book`.`price`, `app_book`.`memo`, `app_book`.`publisher_id`, `app_publisher`.`id`,
    `app_publisher`.`name`, `app_publisher`.`city` FROM `app_book` INNER JOIN `app_publisher`
    ON (`app_book`.`publisher_id` = `app_publisher`.`id`); args=()
  4. 南方传媒出版社
  5. 长沙电视出版社
  6. 长沙电视出版社
  7. 清华大学出版社
  8. 人民邮电出版社
  9. 南方传媒出版社
  10.  
  11. Process finished with exit code 0

结论:

  在对QuerySet使用select_related()函数前,会出现线性的SQL查询,如果存在n个查询对象,每个对象存在k个外键字段时,就会出现n*k + 1 次SQL查询

  在对QuerySet使用select_related()函数后,Django会在进行第一次查询时获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库,只需要进行一次查询

  3)相关参数

  *fields参数,select_related() 接受可变长参数,每个参数是需要获取的外键(父表的内容)的字段名,以及外键的外键的字段名、外键的外键的外键等,若要选择外键的外键需要使用两个下划线“__”来连接    

示例:通过外键获取书籍的出版社的详细地址

  1. obj = models.Book.objects.select_related('publisher__detail').get(title='武林传奇')
    print(obj.publisher.detail)
    print(obj.publisher)

  输出的日志信息    

(0.000) SELECT @@SQL_AUTO_IS_NULL; args=None
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.000) SELECT VERSION(); args=None
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.001) SELECT `app_book`.`id`, `app_book`.`title`, `app_book`.`publish_date`, `app_book`.`price`, `app_book`.`memo`, `app_book`.`publisher_id`, `app_publisher`.`id`, `app_publisher`.`name`, `app_publisher`.`city`, `app_publisher`.`detail_id`, `app_publisherdetail`.`id`, `app_publisherdetail`.`addr`, `app_publisherdetail`.`create_date` FROM `app_book` INNER JOIN `app_publisher` ON (`app_book`.`publisher_id` = `app_publisher`.`id`) INNER JOIN `app_publisherdetail` ON (`app_publisher`.`detail_id` = `app_publisherdetail`.`id`) WHERE `app_book`.`title` = '武林传奇'; args=('武林传奇',)
长沙南区
长沙电视出版社

结论:

  Django使用了2次 INNER JOIN 来完成请求,获得了publisher表和publisherdetail表的内容并添加到结果表的相应列,再次调用这两个对象的时候也不必再次进行SQL查询;反之如是未指定的外键则会进行SQL查询,且深度更深,查询次数更多

  depth参数,select_related() 接受depth参数,depth参数可以确定select_related的深度,Django会递归遍历指定深度内的所有的OneToOneField和ForeignKey

  无参数,select_related() 也可以不加参数,这样表示要求Django尽可能深的select_related

总结:

  1. 1.select_related主要针一对一和多对一关系进行优化
  2. 2.select_related使用SQLJOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能
  3. 3.可以通过可变长参数指定需要select_related的字段名。也可以通过使用双下划线“__”连接字段名来实现指定的递归查询。没有指定的字段不会缓存,没有指定的深度不会缓存,如果要访问的话Django会再次进行SQL查询
  4. 4.也可以通过depth参数指定递归的深度,Django会自动缓存指定深度内所有的字段。如果要访问指定深度外的字段,Django会再次进行SQL查询
  5. 5.也接受无参数的调用,Django会尽可能深的递归查询所有的字段。但注意有Django递归的限制和性能的浪费
  6. 6.Django >= 1.7,链式调用的select_related相当于使用可变长参数。Django < 1.7,链式调用会导致前边的select_related失效,只保留最后一个

2.prefetch_releated()

  对于多对多字段(ManyToManyField)和一对多(ForeignKey)字段,可使用prefetch_related()来进行优化,它和select_releated的设计目的相似,都是为了减少SQL的查询次数,但实现方式不一样,后者是通过JOIN语句在SQL查询内解决问题。但由于JOIN语句过于冗长,不适合解决多对多关系,会导致SQL语句运行时间的增加和内存占用增加

  prefetch_releated的解决方法是分别查询每个表,然后用Python处理他们之间的关系

示例:

  1. book_obj = models.Book.objects.prefetch_related('publisher').get(title='七侠五义')
  2. print(book_obj.publisher.detail)

  输出的日志信息

  1. (0.000) SELECT @@SQL_AUTO_IS_NULL; args=None
  2. (0.001) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
  3. (0.000) SELECT VERSION(); args=None
  4. (0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
  5. (0.000) SELECT `app_book`.`id`, `app_book`.`title`, `app_book`.`publish_date`, `app_book`.`price`, `app_book`.`memo`, `app_book`.`publisher_id` FROM `app_book` WHERE `app_book`.`title` = '七侠五义'; args=('七侠五义',)
  6. (0.000) SELECT `app_publisher`.`id`, `app_publisher`.`name`, `app_publisher`.`city`, `app_publisher`.`detail_id` FROM `app_publisher` WHERE `app_publisher`.`id` IN (1); args=(1,)
  7. (0.001) SELECT `app_publisherdetail`.`id`, `app_publisherdetail`.`addr`, `app_publisherdetail`.`create_date` FROM `app_publisherdetail` WHERE `app_publisherdetail`.`id` = 1; args=(1,)
  8. 广州天河区
  9.  
  10. Process finished with exit code 0

结论:   

  可以看出,第一条查询语句拿到的要操作的对象,关键在于第二条拿到了要查询结果的条件也就是ID,第三行通过第二行拿到的ID进行结果查询

  从第二条查询语句中可看出,可以看见,prefetch使用的是 IN 语句实现的。这样,在QuerySet中的对象数量过多的时候,根据数据库特性的不同有可能造成性能问题

  *lookups参数,prefetch_related()在Django < 1.7 只有这一种用法。和select_related()一样,prefetch_related()也支持深度查询

  要注意的是,在使用QuerySet的时候,一旦在链式操作中改变了数据库请求,之前用prefetch_related缓存的数据将会被忽略掉。这会导致Django重新请求数据库来获得相应的数据,从而造成性能问题。这里提到的改变数据库请求指各种filter()、exclude()等等最终会改变SQL代码的操作。而all()并不会改变最终的数据库请求,因此是不会导致重新请求数据库的

总结:

  1. 1.prefetch_related主要针一对多和多对多关系进行优化
  2. 2.prefetch_related通过分别获取各个表的内容,然后用Python处理他们之间的关系来进行优化
  3. 3.可以通过可变长参数指定需要select_related的字段名。指定方式和特征与select_related是相同的
  4. 4.Django >= 1.7可以通过Prefetch对象来实现复杂查询,但低版本的Django好像只能自己实现
  5. 5.作为prefetch_related的参数,Prefetch对象和字符串可以混用
  6. 6.prefetch_related的链式调用会将对应的prefetch添加进去,而非替换,似乎没有基于不同版本上区别
  7. 7.可以通过传入None来清空之前的prefetch_related

归纳:

  1. 1.因为select_related()总是在单次SQL查询中解决问题,而prefetch_related()会对每个相关表进行SQL查询,因此select_related()的效率通常比后者高
    2.鉴于第一条,尽可能的用select_related()解决问题。只有在select_related()不能解决问题的时候再去想prefetch_related()
  2. 3.可以在一个QuerySet中同时使用select_related()和prefetch_related(),从而减少SQL查询的次数
  3. 4.只有prefetch_related()之前的select_related()是有效的,之后的将会被无视掉

Django框架详细介绍---ORM相关操作---select_related和prefetch_related函数对 QuerySet 查询的优化的更多相关文章

  1. Django框架详细介绍---ORM相关操作

    Django ORM相关操作 官方文档: https://docs.djangoproject.com/en/2.0/ref/models/querysets/ 1.必须掌握的十三个方法 <1& ...

  2. 这个贴子的内容值得好好学习--实例详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化

    感觉要DJANGO用得好,ORM必须要学好,不管理是内置的,还是第三方的ORM. 最最后还是要到SQL.....:( 这一关,慢慢练啦.. 实例详解Django的 select_related 和 p ...

  3. Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化

    引言 在数据库存在外键的其情况下,使用select_related()和prefetch_related()很大程度上减少对数据库的请求次数以提高性能 1.实例准备 模型: from django.d ...

  4. Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(二)

    3. prefetch_related() 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化.或许你会说,没有一个叫OneToMan ...

  5. Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(一)

    在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能.本文通过一个简单的例子详解这两个函数的作用.虽然Q ...

  6. 转载 :实例详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(一)

    在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能.本文通过一个简单的例子详解这两个函数的作用.虽然Q ...

  7. 实例具体解释Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(二)

    这是本系列的第二篇,内容是 prefetch_related() 函数的用途.实现途径.以及用法. 本系列的第一篇在这里 第三篇在这里 3. prefetch_related() 对于多对多字段(Ma ...

  8. 详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化

    在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能.本文通过一个简单的例子详解这两个函数的作用. 1. ...

  9. 转 实例具体解释DJANGO的 SELECT_RELATED 和 PREFETCH_RELATED 函数对 QUERYSET 查询的优化(二)

    https://blog.csdn.net/cugbabybear/article/details/38342793 这是本系列的第二篇,内容是 prefetch_related() 函数的用途.实现 ...

随机推荐

  1. HTML 01 请求过程

    与 HTTP 关系密切的协议, IP, TCP, DNS IP协议的作用是把各种数据包传送给对方, 而要保证确实传送到对方那里, 需要满足各种条件. 其中两个最重要的条件是 IP地址 和 MAC 地址 ...

  2. myeclipse中的项目 如何在项目视窗中显示setting,classpath等配置文件

    导入了别人的项目,各种jar包都放好后,path也都build好了,项目也能正常启动,但是就是项目名有红叉,这是为什么呢? 网上有人说Java build path中的jar包missing了,这是一 ...

  3. [原创] 如何PCB通流能力计算

    一.计算方法如下: 先计算Track的截面积,大部分PCB的铜箔厚度为35um(不确定的话可以问PCB厂家)它乘上线宽就是截面积,注意换算成平方毫米. 有一个电流密度经验值,为15~25安培/平方毫米 ...

  4. 大华等其他NVR接入海康IPC H.264方法

    有一次遇到这个问题,因为时间急,没有注意,这次一个朋友也遇到这个问题,各种百度,也没有看到答案 只好自己研究了一下,最终发现以下方式来解决 下面办法可以解决海康IPC不能能过ONVIF连接到大华等其他 ...

  5. Spring-Cloud-Ribbon学习笔记(二):自定义负载均衡规则

    Ribbon自定义负载均衡策略有两种方式,一是JavaConfig,一是通过配置文件(yml或properties文件). 需求 假设我有包含A和B服务在内的多个微服务,它们均注册在一个Eureka上 ...

  6. 【Excel】输出固定长文本

    '******************************************************************************* ' 固定長形式テキストファイル書き出す ...

  7. LeetCode - 804. Unique Morse Code Words

    International Morse Code defines a standard encoding where each letter is mapped to a series of dots ...

  8. Android学习:代码控制UI界面示例

    package allegro.test2; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; im ...

  9. Android 官方独立 adb / fastboot 工具包

    https://dl.google.com/android/repository/platform-tools-latest-darwin.zip https://dl.google.com/andr ...

  10. 26、jQuery

    一. jQuery简介 (一) jQuery是什么: 是一个javascript代码仓库 是一个快速的简洁的javascript框架,可以简化查询DOM对象.处理事件.制作动画.处理Ajax交互过程. ...