django select_related - 何时使用它

时间:2015-10-20 07:29:40

标签: django select orm

我正在尝试在django中优化我的ORM查询。我使用connection.queries来查看django为我生成的查询。

假设我有这些模型:

class Book(models.Model):
 name= models.CharField(max_length=50)
 author = models.ForeignKey(Author)

class Author(models.Model):
 name = models.CharField(max_length=50)

让我们说,当我生成一个特定的网页时,我希望显示所有书籍,每个书籍旁边都有他们的作者姓名。另外,我单独展示了所有作者。

我应该使用

Book.objects.all().select_related("author")

这将导致JOIN查询。即使我之前做了一行:

Author.objects.all()

显然在模板中我会写一些像{{book.author.name}}的东西 所以问题是,当我访问外键值(作者)时,如果django已经有来自另一个查询的那个对象,那么还是会导致额外的查询(对于每本书)吗? 如果不是,那么在这种情况下,使用select_related实际上是否会产生性能开销?

4 个答案:

答案 0 :(得分:21)

您实际上是在问两个不同的问题:

1。使用select_related确实会产生性能开销吗?

您应该看到有关Django Query Cache的文档:

  

了解QuerySet评估

     

为避免出现性能问题,请务必了解:

     
      
  • QuerySets很懒。

  •   
  • 评估时。

  •   
  • 如何将数据保存在内存中。

  •   

总而言之,Django在同一个QuerySet对象中的内存结果中缓存,也就是说,如果你这样做:

books = Book.objects.all().select_related("author")
for book in books:
    print(book.author.name)  # Evaluates the query set, caches in memory results
first_book = books[1]  # Does not hit db
print(first_book.author.name)  # Does not hit db  

只有在select_related中预取作者时才会命中db一次,所有这些都会导致单个数据库查询 INNER JOIN

但这不会在查询集之间进行任何缓存,甚至也不会使用相同的查询:

books = Book.objects.all().select_related("author")
books2 = Book.objects.all().select_related("author")
first_book = books[1]  # Does hit db
first_book = books2[1]  # Does hit db

实际上已在docs中指出:

  

我们假设您已完成上述明显的事情。本文档的其余部分重点介绍如何以不使用不必要的工作的方式使用Django。本文档也未涉及适用于所有昂贵操作的其他优化技术,例如general purpose caching

2。如果django已经有来自另一个查询的那个对象,那还会导致额外的查询(对于每本书)吗?

如果Django执行 ORM查询缓存,您实际上是有意义的,这是一个非常不同的事情。 ORM查询缓存,也就是说,如果您在之前执行查询,然后执行相同的查询以后,如果数据库没有更改,则结果来自缓存,而不是昂贵的数据库查找。

答案不是Django,不是官方支持,而是非正式的,是的,通过第三方应用程序。支持此类缓存的最相关的第三方应用是:

  1. Johnny-Cache(较旧,不支持django> 1.6)
  2. Django-Cachalot(较新,支持1.6,1.7,仍然在开发1.8中)
  3. Django-Cacheops(较新,支持Python 2.7或3.3 +,Django 1.8+和Redis 2.6+(推荐4.0+))
  4. 如果您查找查询缓存并记住,首先查看配置文件,查找瓶颈,以及它们是否导致问题然后进行优化,请查看这些内容。

      

    真正的问题是程序员花了太多时间在错误的地方和错误的时间担心效率;过早优化是编程中所有邪恶(或至少大部分)的根源。唐纳德克努特。

答案 1 :(得分:17)

Django不了解其他查询! Author.objects.all()Book.objects.all()是完全不同的查询集。因此,如果您在视图中同时将它们传递给模板上下文,但在模板中执行以下操作:

{% for book in books %}
  {{ book.author.name }}
{% endfor %}

并且 N 这些书籍会导致 N 额外的数据库查询(超出查询以获取所有图书和作者)!

如果您已完成Book.objects.all().select_related("author"),则不会在上述模板代码段中进行额外查询。

现在,select_related()当然会增加查询的开销。当您执行Book.objects.all() django时,会返回SELECT * FROM BOOKS的结果。相反,如果您执行Book.objects.all().select_related("author") django将返回结果 SELECT * FROM BOOKS B LEFT JOIN AUTHORS A ON B.AUTHOR_ID = A.ID。因此,对于每本书,它将返回书籍的列和相应的作者。但是,与点击数据库 N 次的开销(如前所述)相比,这种开销实际上要小得多。

因此,即使select_related创建了一个小的性能开销(每个查询从数据库中返回更多字段),除非您完全确定自己需要您要查询的特定模型的列。

最后,真正了解数据库中有多少(以及哪些确切)查询可以使用django-debug-tooblar(https://github.com/django-debug-toolbar/django-debug-toolbar)的好方法是使用django-debug-tooblar({{3}})。

答案 2 :(得分:2)

与选择相关

select_related是一个可选的性能提升器,通过它可以进一步访问 Queryset 中的foreign_keys属性,而不会访问数据库。

Design philosophies

这也是为什么存在select_related()QuerySet方法的原因。对于选择“每个相关对象”的常见情况,它是可选的性能提升器。

Django official doc

返回一个查询集,该查询集将“遵循”外键关系,并在执行查询时选择其他相关对象数据。这可以提高性能,从而导致单个更复杂的查询,但意味着以后使用外键关系将不需要数据库查询。

如定义中所述,在 foreign_key关系中仅允许使用select_related。忽略此规则将面临以下异常:

In [21]: print(Book.objects.select_related('name').all().query)

FieldError: Non-relational field given in select_related: 'name'. Choices are: author

让我们深入研究一个例子:

这是我的models.py。 (与询问的问题相同)

from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

    __repr__ = __str__


class Book(models.Model):
    name = models.CharField(max_length=50)
    author = models.ForeignKey(Author, related_name='books', on_delete=models.DO_NOTHING)

    def __str__(self):
        return self.name

    __repr__ = __str__
  • 使用relect_related增强器来获取所有书籍及其作者的内容:
In [25]: print(Book.objects.select_related('author').all().explain(verbose=True, analyze=True))
Hash Join  (cost=328.50..548.39 rows=11000 width=54) (actual time=3.124..8.013 rows=11000 loops=1)
  Output: library_book.id, library_book.name, library_book.author_id, library_author.id, library_author.name
  Inner Unique: true
  Hash Cond: (library_book.author_id = library_author.id)
  ->  Seq Scan on public.library_book  (cost=0.00..191.00 rows=11000 width=29) (actual time=0.008..1.190 rows=11000 loops=1)
        Output: library_book.id, library_book.name, library_book.author_id
  ->  Hash  (cost=191.00..191.00 rows=11000 width=25) (actual time=3.086..3.086 rows=11000 loops=1)
        Output: library_author.id, library_author.name
        Buckets: 16384  Batches: 1  Memory Usage: 741kB
        ->  Seq Scan on public.library_author  (cost=0.00..191.00 rows=11000 width=25) (actual time=0.007..1.239 rows=11000 loops=1)
              Output: library_author.id, library_author.name
Planning Time: 0.234 ms
Execution Time: 8.562 ms

In [26]: print(Book.objects.select_related('author').all().query)
SELECT "library_book"."id", "library_book"."name", "library_book"."author_id", "library_author"."id", "library_author"."name" FROM "library_book" INNER JOIN "library_author" ON ("library_book"."author_id" = "library_author"."id")

如您所见,使用select_related会导致提供的外键(此处为author)上的 INNER JOIN

执行时间:

  • 使用计划者选择的最快计划来运行查询
  • 返回结果

8.562毫秒

另一方面:

  • 无需使用relect_related辅助程序即可获取所有书籍及其作者的内容:
In [31]: print(Book.objects.all().explain(verbose=True, analyze=True))
Seq Scan on public.library_book  (cost=0.00..191.00 rows=11000 width=29) (actual time=0.017..1.349 rows=11000 loops=1)
  Output: id, name, author_id
Planning Time: 1.135 ms
Execution Time: 2.536 ms

In [32]: print(Book.objects.all().query)
SELECT "library_book"."id", "library_book"."name", "library_book"."author_id" FROM "library_book

如您所见,这只是对仅包含 author_id 的图书模型的简单 SELECT 查询。在这种情况下,执行时间为 2.536 ms

如Django doc中所述:

对外键属性的进一步访问将对数据库造成另一次攻击:(CUZ我们还没有)

In [33]: books = Book.objects.all()

In [34]: for book in books:
    ...:     print(book.author) # Hit the database

另请参阅QuerySet API参考中的Database access optimizationexplain()

Django Database Caching:

Django带有一个强大的缓存系统,可让您保存动态页面,因此不必为每个请求都计算它们。为方便起见,Django提供了不同级别的缓存粒度:您可以缓存特定视图的输出,可以仅缓存难以生成的片段,或者可以缓存整个站点。

Django也可以与“下游”缓存(例如Squid和基于浏览器的缓存)配合使用。这些是您不直接控制的缓存类型,但您可以通过HTTP标头向它们提供提示,以提示您应缓存网站的哪些部分以及如何缓存。

您应该阅读这些文档,以找出最适合您的文档。


PS1::有关规划器及其工作方式的更多信息,请参见Why Planing time and Execution time are so different Postgres?Using EXPLAIN


答案 3 :(得分:1)

var myArray = [
    {"value1":"Value1Here", "value2":"Value2Here"},
    {"value1":"Value1Here", "value2":"Value2Here"},
    {"value1":"Value1Here", "value2":"Value2Here"}
];

足够好了。无需Book.objects.select_related("author")

Author.objects.all()

不会命中数据库,因为{{ book.author.name }} 已经预先填充了。