Django 中的 select_related 和 prefetch_related

2024年8月29日 | 阅读 7 分钟

Django ORM 代表对象关系映射器,用于处理数据库,我们可以用 Pythonic 的方式存储数据。本教程将详细解释 Django ORM 查询优化中的一个重要概念 - select_relatedpre_fetch related。

Django 中的 select_related 是什么?

当我们从数据库中获取任何对象时,它返回的是新的 queryset,由该对象组成,而不是特定的对象。它将在访问时创建所有相关对象的新 queryset。这并非在所有情况下都适用。

在进行 select_related 操作之前,我们应该在 settings.py 文件中添加以下日志记录,以查看 Django CRM 中运行的所有 SQL 查询。

将以下代码段与 settings.py 中的 LOGGING 字段合并

Model.py

我们创建了两个模型,一个 Movie 和一个 Director。Movie 模型有三个字段,包括 movie_title, release_year,director 外键

现在我们将运行以下命令以在数据库中创建这些表。

我们将打开 Django shell 并在其中创建对象以在表中存储数据。

在 Django shell 中创建 director 对象。

我们使用以下命令获取 directors 的 queryset。

我们可以看到内部运行的相应 SQL 查询。

现在我们将创建 movie 模型对象。

上面的输出显示了 movie 对象的 SQL 查询。 同样,我们创建了三个 movie 对象。

Movie 表与 Director 表具有外键关系。因此,我们可以使用以下查询来获取数据

如上所示,我们需要运行一个单独的查询来使用 Movie 对象获取 director 姓名。

这些用于相关对象的单独查询会降低应用程序的性能。 假设我们有 1000 部电影,并且我们必须创建一个包含作者姓名的电影列表。

每次我们访问外键时,都需要进行另一个查询以检索该值。因此,最终我们将运行 1001 个查询以获取书籍列表。

为了克服这个问题,Django 提供了 select_related(),它会将 1001 个查询减少到 1 个。

Select Related

select_related 在表中执行 inner join 操作。在下面的示例中,movie_id 与 director.id 进行 inner join。

示例 -

让我们创建一个查询,以 1 个查询获取所有电影和导演姓名。

打开 Django shell 并输入以下查询。

正如我们在输出中看到的,只调用了一个 join 查询来获取所有电影及其相关的导演。这对应用程序来说是一个很大的改进。

select_related() 和 all() 之间的区别

  • 没有 select_related
  • 使用 select_related

现在我们将使用 all() 方法运行查询。

我们从这两个查询中得到相同的数据,但在查询查找方面存在差异。

让我们看看另一种情况,我们想获取第一部电影的导演姓名。

  • 没有 select_related

我们可以观察到有两个 SQL 查询被触发以获取导演姓名。第一个查询获取电影名称,第二个查询获取相关的导演姓名。这可能会导致应用程序中出现很多冗余。 让我们看看如何使用 单个查询 来实现这一点。

  • 使用 select_related

select_related 仅限于外键关系。 如果存在多对多关系,那么我们不能使用 select_related。 在这种情况下,我们可以使用 prefech_related。

Prefetch Related

prefetch_related 可以与多对多关系一起使用,以通过减少查询数量来提高性能。 让我们理解以下示例。

当我们尝试获取带有发布者的电影时,它会在后台运行两个 SQL 查询

我们可以使用 prefetch_related() 解决这个问题。 让我们理解以下示例。

结论

到目前为止,我们已经看到了 select_relatedprefetch_related 如何有效地减少 Django 中的查询开销。 select_related 用于外键关系,它使用关联表执行 INNER join 操作。

另一方面,prefech_related 与多对多关系一起使用。 Select_related 通过多表联接关联查询同时获取所有数据,并通过减少数据库查询来提高性能。

但在多对多关系中,使用 JOIN 解决它们不是一个好选择,因为它将是一个非常漫长而乏味的过程。 最终会非常耗时,并且 SQL 语句。 为了解决这个问题,我们将使用 prefetch_related