django select_related - 何时使用它

Posted

技术标签:

【中文标题】django select_related - 何时使用它【英文标题】:django select_related - when to use it 【发布时间】:2016-01-18 18:02:42 【问题描述】:

我正在尝试在 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 实际上会产生性能开销吗?

【问题讨论】:

【参考方案1】:
Book.objects.select_related("author")

足够好。不需要Author.objects.all()

 book.author.name 

不会访问数据库,因为 book.author 已经预先填充。

【讨论】:

我知道。但考虑到我也查询所有作者的事实,我想知道那个 JOIN 查询是否会浪费。无论如何,我确实需要 Author.objects.all(),正如我所说,它是单独打印在同一页面中的。 @user3599803 如果你想显示这本书的作者,那么你需要 select_related() 否则你有更多的 db hits。 如果你使用Author.objects.all(),它也会命中db,从而增加执行时间。【参考方案2】:

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

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

如果有 N 本书,这将导致 N 个额外的数据库查询(超出获取所有图书和作者的查询)!

如果您已经完成Book.objects.all().select_related("author"),则不会在上述模板 sn-p 中完成额外的查询。

现在,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 会产生很小的性能开销(每个查询都会从数据库中返回更多字段),但实际上使用它是有益的,除非您完全确定您只需要 em> 您正在查询的特定模型的列。

最后,真正了解您的数据库中实际执行了多少(以及究竟是哪些)查询的好方法是使用 django-debug-tooblar (https://github.com/django-debug-toolbar/django-debug-toolbar)。

【讨论】:

谢谢。如果我的 Author 对象更大一些,意味着它有更多的字段,我只需要在本书中选择作者的名字。写出来是否合法,性能更好:Book.objects.all().select_related("author_name")。 嗯,我认为是的,它会合法且更好,因为您只会获得所需的字段。但是,请像 Book.objects.all().select_related('author__name') 一样尝试。另外请检查我的更新 我不明白你为什么这样做.all() 它确实提出了另一个问题,我在哪里使用 .all() 有什么区别吗?意思是,Book.objects.all().select_related("author") 或 Book.objects.select_related("author").all() 之间有什么区别。或者实际上应该完全避免 all() ?由于我读到 all() 是可调用的,因此总是命中数据库。【参考方案3】:

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

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

你应该看到关于Django Query Cache的文档:

了解 QuerySet 评估

为避免性能问题,了解以下几点很重要:

QuerySet 是惰性的。

当它们被评估时。

数据如何保存在内存中。

因此,总而言之,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 中预取 Authors 时,只会命中 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,没有官方支持,但非官方支持,通过 3rd-party 应用程序支持。启用此类缓存的最相关的第三方应用是:

    Johnny-Cache(老,不支持django>1.6) Django-Cachalot(更新,支持 1.6、1.7,仍在开发 1.8 中) Django-Cacheops(较新,支持 Python 2.7 或 3.3+、Django 1.8+ 和 Redis 2.6+(推荐 4.0+))

如果您要查找查询缓存,请查看那些,并记住,首先配置文件,找到瓶颈,如果它们导致问题,然后进行优化。

真正的问题是程序员在错误的地方和错误的时间花费了太多时间来担心效率;过早的优化是编程中万恶之源(或至少大部分)。唐纳德·高德纳。

【讨论】:

我不认为我打算缓存整个查询。我的意思是,因为我在上一个查询中拥有所有作者(就像我所做的 Author.objects.all() 一样),所以稍后执行 book.author.name 不应该导致查询,因为 django 可以以某种方式从作者查询集中获取它。但我意识到它不起作用。 好吧,你确认你“期望”查询缓存。 Django 在 QuerySet 级别缓存,如果你有两个对象,每个对象都有不同的缓存。如果你说你执行了一个 Author.objects.all(),没有 select_related,它会导致一个查询,因为 FK 是惰性的。如果您添加 select_related 它不会再次使用相同的 QuerySet 对象命中 db。如果您在代码的另一点执行另一个 Author.objects.all(),则不会缓存先前的结果,并将在新的 QuerySet 中再次评估。当你说你以前做过 Author.objects.all() 时,问题可能还不够清楚。 我认为 select_related 确实是左外连接而不是内连接。【参考方案4】:

选择相关

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

Design philosophies

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

Django official doc

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

正如定义中所指出的,只允许在 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 会导致在提供的外键上出现INNER JOIN(这里是author)。

执行时间其中的时间:

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

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 毫秒

如 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 optimization 和 explain()

Django Database Caching:

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

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

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


PS1:有关规划器及其工作原理的更多信息,请参阅Why Planing time and Execution time are so different Postgres? 和Using EXPLAIN)


【讨论】:

以上是关于django select_related - 何时使用它的主要内容,如果未能解决你的问题,请参考以下文章

如何使用'select_related'从相关(ForeignKey)django模型中接收并非所有字段

Django:select_related() 的用法和执行时间性能

django 2.1 上的 select_related 不工作

如何在模板 Django 中使用 select_related?

Django中的左外反向select_related?

Django 通用外键和 select_related