记录 django 中的查询优化
chenzuoqing Lv3

记录 django 中的查询优化

恰当的使用 select_relatedprefetch_related 方法,可以减少数据库重复查询的次数

两种方法均支持双下划线指定需要查询的关联对象的字段名

  • select_related
    • 适合一对一,一对多的外键字段
  • prefetch_related:
    • 适合多对多字段、外键反查(related_name)的情况
    • 在方法中使用 Prefetch 可以增加查询条件
    • 执行两次数据库查询

示例 model

1
2
3
4
5
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)

适用于外键这种一对一,一对多的情况,执行其实是生成一条 inner join 的 SQL 语句,一次查询获取对象和关联对象的内容。

使用方法如下,遍历结果集 articles 调用 item.category.name 获取信息时,不产生另外的查询:

1
2
3
4
5
6
7
8
9
10
# 获取对象的同时,获取相关的字段信息
articles = Article.objects.all().select_related('category')
articles = Article.objects.all().select_related('category__name')
articles = Article.objects.all().select_related('category', 'author__name')

# 不指定字段,直接使用select_related()将查出所有关联信息
articles = Article.objects.all().select_related()

# 再加 filter 组合,顺序不重要
articles = Article.objects.all().filter(pk__in=(1, 2, 3)).select_related()

在模板中遍历的示例,未使用 select_related 方式:

1
2
3
4
5
6
7
8
9
10
11
12
<ul>
{% for article in articles %}
<li>{{ article.title }} </li>
{# category.name 每次都将生成一条查询。若按上述方法,这里将从对象中直接拿到,不用额外的一次查询 #}
<li>{{ article.category.name }}</li>
<li>
{% for tag in article.tags.all %}
{{ tag.name }}, {# 每次都将生成一条查询 #}
{% endfor %}
</li>
{% endfor %}
</ul>

对于多对多的字段,不能使用 select_related 方式,避免多对多字段 join 后结果很大。prefect_related 就是用于多对多关系的,也可以用于外键的反查(related_name)

使用方法如下,遍历结果集,拿关联的 tags 对象信息时,不用每次遍历执行一次查询

1
2
3
4
5
6
7
8
# 获取对象列表并加载关联 tags 对象的 name 字段
articles = Article.objects.all().prefetch_related('tags__name')

# 获取关联对象 tags 的所有信息
articles = Article.objects.all().prefetch_related('tags')

# 上面模版中的例子,可以使用如下查询,加载 category 和 tags 对象信息
articles = Article.objects.all().select_related('category__name').prefetch_related('tags')

在 prefetch_related 中,还可以对所查的关联对象进行过滤

1
2
3
4
5
6
7
8
9
10
# 获取文章列表及每篇文章相关的名字以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'
)
 Comments