在前面教程中小编我已经介绍了Django的Queryset特性及高级使用技巧以及Queryset的aggregate和annotate方法。这些技巧和方法都是为了减少对数据库的访问次数和对内存的占用,从而提升网站性能。今天我们再来学习两个非常重要的查询方法select_related和prefetch_related方法,看看如何使用它们避免不必要的数据库查询。高手过招,只差分毫。专业和业余之前的区别就在细节的处理上。为了让大家更直观地看到这两个方法的作用,我们将安装使用django-debug-toolbar这个流行的Django第三方包

django-debug-toolbar的安装

第一步:pip install django-debug-toolbar

第二步:打开项目文件夹settings.py 文件, 把"debug_toolbar"加到INSTALLED_APP里去。

第三步: 打开项目文件夹里的urls.py, 把debug_toolbar的urls加进去。

from django.conf import settings
from django.conf.urls import include, url  # For django versions before 2.0
from django.urls import include, path  # For django versions from 2.0 and upif settings.DEBUG:import debug_toolbarurlpatterns = [path('__debug__/', include(debug_toolbar.urls)),] + urlpatterns

第四步: 在settings.py里添加中间件

MIDDLEWARE = [# ...'debug_toolbar.middleware.DebugToolbarMiddleware',# ...
]
第五步:  在settings.py设置本地IP, debug_toolbar只能在localhost本地测试环境下运行。
INTERNAL_IPS = [# ...'127.0.0.1',# ...
]

当你安装好debug_toolbar后,启动django服务器,打开任何一个页面你都可以看到查询数据库所花时间以及是否有相似及重复的查询,如下图所示:

言归正传

假设我们有如下一个文章(Article)模型,其与类别(Category)是单对多地关系(ForeignKey), 与标签(Tag)是多对多的关系(ManyToMany)。我们需要编写一个article_list的函数视图,以列表形式显示文章清单及每篇文章的类别和标签,我们常规做法如下:

#models.py

class Article(models.Model):"""文章模型"""title = models.CharField('标题', max_length=200, db_index=True)category = models.ForeignKey('Category', verbose_name='分类',
on_delete=models.CASCADE, blank=False, null=False)tags = models.ManyToManyField('Tag', verbose_name='标签集合', blank=True)

#views.py

def article_list(request):articles = Article.objects.all()return render(request, 'blog/article_list.html',
{'articles': articles, })

我们的模板代码会如下所示:

#blog/article_list.html

<ul>
{% for article in articles %}<li>{{ article.title }} </li><li>{{ article.category.name }}</li><li>{% for tag in article.tags.all %}{{ tag.name }},{% endfor %}</li>
{% endfor %}
</ul>

上面代码运行是没有错的,只是效率很低。使用debug_toolbar可以让我们更深入地看问题。它提示我们查询了10次数据库,包括3次重复查询,一共耗时8.93ms。

什么?显示一个页面竟用了10次查询?是的,你没看错。我们先分析下这会什么会发生,然后再解释如何使用select_related和prefetch_related方法解决这个问题。

为什么会有重复查询?

当我们使用Article.objects.all()查询文章时,我们做了第一次数据库查询,查询的是blog_article数据表, 得到的数据只是文章对象列表,然而并没有包含与每篇文章相关联的category和tags对象信息。当我们在模板中调用{{ article.category.name }} 和 {{ tag.name }}显示category和tags的名字时,Django还需要重新查询blog_category和blog_tag数据表获取名字。for循环每运行一次,django都要对数据库进行一次查询,造成了极大的资源浪费。为什么我们不能再第一次获取文章列表的同时就获取每篇文章相关联的category和tags对象信息呢?Django考虑到了这一点,所以提供select_related和prefetch_related方法来提升数据库查询效率,类似于SQL的JOIN方法。

select_related方法

select_related将会根据外键关系(注意: 仅限单对单和单对多关系),在执行查询语句的时候通过创建一条包含SQL inner join操作的SELECT语句来一次性获得主对象及相关对象的信息。现在我们对article_list视图函数稍微进行修改,加入select_related方法,在查询文章列表时同时一次性获取相关联的category对象信息,这样在模板中调用 {{ article.category.name }}时就不用再查询数据库了。

def article_list(request):articles = Article.objects.all().select_related('category')return render(request, 'blog/article_list.html',
{'articles': articles, })

运行结果如下图所示,查询次数由10次变为了7次,时间降到了2.99ms。

selected_related常用使用案例如下:

# 获取id=13的文章对象同时,获取其相关category信息
Article.objects.select_related('category').get(id=13)# 获取id=13的文章对象同时,获取其相关作者名字信息
Article.objects.select_related('author__name').get(id=13)# 获取id=13的文章对象同时,获取其相关category和相关作者名字信息。下面方法等同。
Article.objects.select_related('category', 'author__name').get(id=13)
Article.objects.select_related('category').select_related('author__name').get(id=13)# 使用select_related()可返回所有相关主键信息。all()非必需。
Article.objects.all().select_related()# 获取Article信息同时获取blog信息。filter方法和selected_related方法顺序不重要。
Article.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Article.objects.select_related('blog').filter(pub_date__gt=timezone.now())

prefetch_related方法

对于多对多字段,你不能使用select_related方法,这样做是为了避免对多对多字段执行JOIN操作从而造成最后的表非常大。Django提供了prefect_related方法来解决这个问题。prefect_related可用于多对多关系字段,也可用于反向外键关系(related_name)。我们对之前的article_list视图函数再做进一步修改,在查询文章列表的同时返回相关tags信息。

def article_list(request):articles = Article.objects.all().select_related('category').prefecth_related('tags')return render(request, 'blog/article_list.html',
{'articles': articles, })

运行结果如下。查询次数减少到5次,运行时间1ms,是不是很帅?

prefetch_related使用方法如下:

# 文章列表及每篇文章的tags对象名字信息
Article.objects.all().prefetch_related('tags__name')# 获取id=13的文章对象同时,获取其相关tags信息
Article.objects.prefetch_related('tags').get(id=13)

现在问题来了,如果我们获取tags对象时只希望获取以字母P开头的tag对象怎么办呢?我们可以使用Prefetch方法给prefect_related方法添加条件和属性。

# 获取文章列表及每篇文章相关的名字以P开头的tags对象信息
Article.objects.all().prefetch_related(Prefetch('tags', queryset=Tag.objects.filter(name__startswith="P"))
)# 文章列表及每篇文章的名字以P开头的tags对象信息, 放在article_p_tag列表
Article.objects.all().prefetch_related(Prefetch('tags', queryset=Tag.objects.filter(name__startswith="P")),
to_attr='article_p_tag'
)

小结

当你查询单个主对象或主对象列表并需要在模板或其它地方中使用到每个对象的关联对象信息时,请一定记住使用select_related和prefetch_related一次性获取所有对象信息,从而提升数据库查询效率,避免重复查询。如果不确定是否有重复查询,可使用django-debug-toolbar查看。

  • 对与单对单或单对多外键ForeignKey字段,使用select_related方法
  • 对于多对多字段和反向外键关系,使用prefetch_related方法
  • 两种方法均支持双下划线指定需要查询的关联对象的字段名
  • 使用Prefetch方法可以给prefetch_related方法额外添加额外条件和属性。

关联阅读

  • Django QuerySet查询基础与技巧。有了她,再也不用担心SQL注入了。
  • Django基础(12): 深夜放干货。QuerySet特性及高级使用技巧,如何减少数据库的访问,节省内存,提升网站性能。
    Django基础(24): aggregate和annotate方法使用详解与示例

大江狗-【Python Web与Django开发】公众号

2019.9.5

union和union all有什么区别_Django基础(29):select_related和prefetch_related的用法与区别...相关推荐

  1. python中sort和sorted区别_Python中的 sort 和 sorted的用法与区别

    今天在做一道题时,因为忘了Python中sort和sorted的用法与区别导致程序一直报错,找了好久才知道是使用方法错误的问题!现在就大致的归纳一下sort和sorted的用法与区别 1. sort: ...

  2. python 类方法 实例方法的区别_python基础教程Python实例方法、类方法、静态方法区别详解...

    1.关于参数的区别 实例方法:定义实例方法是最少有一个形参 ---> 实例对象,通常用 self 类方法:定义类方法的时候最少有一个形参 ---> 类对象,通常用 cls 静态方法:定义静 ...

  3. php union all,Union与Union All的区别

    Union与Union All的区别 如果我们需要将两个select语句的结果作为一个整体显示出来,我们就需要用到union或者union all关键字.union(或称为联合)的作用是将多个结果合并 ...

  4. Union和Union All的区别

    Union和Union All的区别 假设我们有一个表Student,包括以下字段与数据: drop table student;   create table student ( id int pr ...

  5. Union和Union All到底有什么区别

    转自:https://www.cnblogs.com/wen-zi/p/9133754.html 以前一直不知道Union和Union All到底有什么区别,今天来好好的研究一下,网上查到的结果是下面 ...

  6. UNION和UNION ALL有什么区别?

    UNION和UNION ALL什么区别? #1楼 您可以通过运行以下查询来避免重复,并且运行速度仍然比UNION DISTINCT(实际上与UNION相同)快得多: SELECT * FROM myt ...

  7. sql中union 和 union all的区别

    最近发现一个视图出奇的慢,在生产环境还好,由于服务器配置较高,没有察觉出来.但是做了一次修改后在开发版 和测试版就直接查询不出结果了.就连select count(1) from 都运行2个小时没有结 ...

  8. Oracle中的Union、Union All、Intersect、Minus 使用用法区别

      Oracle中的Union.Union All.Intersect.Minus 众所周知的几个结果集集合操作命令,今天详细地测试了一下,发现一些问题,记录备考. 假设我们有一个表Student,包 ...

  9. SQL Union 和 Union All 的区别以及二者的性能问题 - 使用Sqlite演示

    1 Union 和 Union All 的区别 Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序: Union All:对两个结果集进行并集操作,包括重复行,不进行排序: 也 ...

最新文章

  1. js组合模式和寄生组合模式的区别研究
  2. C# 系统应用之ListView控件 (三).添加ContextMenuStrip右键菜单打开删除文件
  3. 2021-02-25
  4. 关于寻路算法的一些思考(6):预先计算好的路径的所用空间
  5. 天池 在线编程 Character deletion
  6. css 百分比 怎么固定正方形_css样式写出三角形,宽高自适应的正方形,扇形!...
  7. 分拣外观残缺的机器人_复合机器人AGV+协作机器人的应用领域
  8. 前端关于html的面试题,关于java:前端面试HTML面试题汇总
  9. 十八、K8s升级集群
  10. 转:KVC与KVO机制
  11. MVC 发布到 windows2003遇到 'System.Web.WebPages.Razor 错误提示
  12. ETL工具 DataX数据同步,LINUX CRONTAB 定时调度
  13. 【图像压缩】基于matlab行程编码(RLE)图像压缩【含Matlab源码 404期】
  14. 误差修正ECM模型怎么分析?
  15. 网易云信 android,网易云信/NIM_Android_UIKit
  16. Unity是如何实现《狂野飙车》实时联网赛车游戏呢?
  17. php实现发送微信模板消息的方法
  18. Definition of Dichotomy
  19. 用计算机弹出记事本,win7电脑开机就会弹出Desktop.ini记事本怎么办?
  20. clang vectorization

热门文章

  1. 音视频技术开发周刊 | 150
  2. 万物皆可“小程序”——迟到的iOS 14之猜想
  3. FFmpeg代码导读——HEVC在RTMP中的扩展
  4. Java多线程之多线程工程代码编写思维方式
  5. 如何有效地进行代码 Review?
  6. 对话腾讯安全杨勇:产业互联网带来哪些新的安全挑战
  7. ubuntu16.04安装,使用redis布隆过滤器示例
  8. 一份非常完整的MySQL规范
  9. leetcode 230. Kth Smallest Element in a BST | 230. 二叉搜索树中第K小的元素(Java)
  10. leetcode 594. Longest Harmonious Subsequence | 594. 最长和谐子序列