Django基础(29): select_related和prefetch_related的用法与区别


在前面教程中小编我已经介绍了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 up

if settings.DEBUG:
import debug_toolbar
urlpatterns = [
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方法额外添加额外条件和属性。

关联阅读

大江狗原创

2019.9.5

  • 25
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: `select_related` 是 Django ORM 中的一个方法,用于查询关联模型的数据。它可以避免进行多次数据库查询,从而提高查询效率。 例如,如果你有一个模型 `Author` 和一个模型 `Book`,并且一个作者可以对应多本书,那么你可以使用以下代码来获取一个作者对应的所有书: ``` author = Author.objects.get(pk=1) books = Book.objects.filter(author=author) ``` 这样会导致两次数据库查询,第一次查询获取作者的数据,第二次查询获取书的数据。 如果使用 `select_related`,就可以一次性获取作者和对应的所有书: ``` author = Author.objects.select_related().get(pk=1) books = author.book_set.all() ``` 这样只需要进行一次数据库查询,查询速度会更快。 ### 回答2: select_relatedDjango ORM的一个功能,用于优化查询性能。它通过使用JOIN操作一次性将多个相关模型的数据取出,避免了多次查询数据库的问题。 在默认情况下,当我们访问一个模型对象的外键或者一对一关联字段时,Django会自动发出相应的数据库查询。这意味着在处理大量对象时,会导致大量查询操作,严重影响性能。 而select_related的作用就是通过指定需要一起查询的相关模型,一次性将这些相关模型的数据取出,避免多次查询。这样,我们就可以在一次数据库访问中获取所有相关模型的数据。使用select_related可以大幅减少数据库的访问次数,提高查询性能。 使用select_related非常简单,只需要在查询时使用select_related()方法,并指定需要查询的相关模型即可。如下所示: Model.objects.select_related('related_model') 其中,Model是主要要查询的模型类,related_model是Model的一个关联字段。使用select_related后,查询结果将包含主要模型和相关模型的所有数据。 需要注意的是,使用select_related查询可能会导致较大的数据集被加载到内存中,如果查询结果集很大,可能会导致性能问题。因此,在使用select_related时,应该根据实际情况谨慎使用,避免查询结果集过大。 综上所述,select_relatedDjango ORM中的一个优化查询性能的功能,通过一次性将多个相关模型的数据取出,避免了多次查询数据库的问题。使用方法简单,在查询时使用select_related()方法,并指定需要查询的相关模型即可。但需要注意查询结果集的大小,避免性能问题的发生。 ### 回答3: select_relatedDjango ORM中的一个方法,用于在查询数据库时进行关联查询,减少查询次数,提高查询效率。它可以在一次查询中同时查询多个相关联的表,而不需要多次查询。 select_related方法的使用非常简单,只需要在查询时使用它即可。例如,假设有一个模型A与模型B关联,我们可以通过以下方式使用select_related: A.objects.select_related('B').filter(条件) 这里的条件可以是任意的查询条件,比如过滤某些数据。select_related('B')表示在查询A模型时同时查询与之关联的B模型。 使用select_related方法的好处是,它会执行一次SQL查询,将A模型和B模型的数据一起返回。这样可以避免在使用A模型数据时,每次都去查询关联的B模型数据,从而减少了数据库查询的次数,提高了查询效率。 需要注意的是,select_related只能进行一层的关联查询,不能进行多层的关联。如果要进行多层关联查询,可以使用prefetch_related方法。 另外,使用select_related方法还需谨慎,因为它会将所有关联的数据一起查询出来,如果关联的数据量很大,会占用大量内存。所以,在使用时要根据实际情况考虑是否使用select_related,并合理设计数据库的关联关系。 总之,select_relatedDjango ORM中非常实用的方法,可以在一次查询中同时查询多个相关联的表,减少数据库查询次数,提高查询效率。但需要注意使用时的内存占用问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值