【问题标题】:Fetch all Django objects that have a M2M relationship获取所有具有 M2M 关系的 Django 对象
【发布时间】:2020-09-16 23:31:23
【问题描述】:

我有以下两个 Django 模型:

class Parent(models.Model):
    name = models.CharField(max_length=50)
    children = models.ManyToManyField("Child", through="ParentChild")

    def __str__(self):
        return self.name

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

    def __str__(self):
        return self.name

假设我的数据库中有四个 Parent 对象,其中只有两个有子对象:

for parent in Parent.objects.all():
    print(parent, parent.children.count())
# Parent1 3
# Parent2 0
# Parent3 0
# Parent4 1

我的目标是编写一个高效的数据库查询来获取至少有一个孩子的所有父母。实际上,我有数百万个对象,所以我需要它尽可能高效。到目前为止,我已经提出了以下解决方案:

  1. 使用prefetch_related
for parent in Parent.objects.prefetch_related("children"):
    if parent.children.exists():
        print(parent)
# Parent1
# Parent4
  1. 使用filter:
for parent in Parent.objects.filter(children__isnull=False).distinct():
    print(parent)
# Parent1
# Parent4
  1. 使用exclude:
for parent in Parent.objects.exclude(children__isnull=True):
    print(parent)
# Parent1
# Parent4
  1. 使用annotateexclude
for parent in Parent.objects.annotate(children_count=Count("children")).exclude(children_count=0):
    print(parent)
# Parent1
# Parent4

这些解决方案中哪个最快?还有另一种更快/更具可读性的方法吗?我看到了一个 django Exists 函数,但它似乎不适用于这个用例。

【问题讨论】:

    标签: django django-models django-orm


    【解决方案1】:

    .prefetch_related(…) 将(可能)没有帮助,因为.exists() 不会使用预取,而是进行存在查询,从而导致 N+1 问题。

    您可以简单地过滤存在非NULL 子元素的事实,并使用.distinct() 检索每个父元素一次:

    Parent.objects.filter(<b>children__isnull=False</b>)<b>.distinct()</b>

    您也可以尝试使用Exists 子查询:

    from django.db.models import Exists, OuterRef
    
    # since Django-3.0
    
    Parent.objects.filter(Exists(
        ParentChild.objects.filter(parent_id=OuterRef('pk'))
    ))

    之前,您可以使用.annotate(…),但这可能会降低效率:

    from django.db.models import Exists, OuterRef
    
    # before Django-3.0
    
    Parent.objects.annotate(has_children=Exists(
        ParentChild.objects.filter(parent_id=OuterRef('pk'))
    )).filter(has_children=True)

    然而,确切的性能取决于数据库,因此最好对查询进行基准测试。它有时还取决于特定的数据库系统:MySQL 数据库可以有与 PostgreSQL 数据库不同的基准。

    【讨论】:

      【解决方案2】:

      这是一个相当开放的问题......

      • 首先,与所有优化一样:

        • 衡量基准性能(基于真实数据或真实数据),这样您就知道自己改进了多少;和
        • 确定令人满意的性能(从业务角度),或者您期望从额外性能中获得什么好处。
      • 几种方法:

        • 在与基线相同的数据上测量所有变体。这是唯一确定的方法。

        • Django 提供了统计(或记录)所有查询的工​​具;你试过用这些吗?在大多数情况下,查询越少越快,尤其是当数据库在(或将)在自己的服务器上时。

        • 一旦你有了查询,在数据库上检查它们,使用 EXPLAIN 或 SHOWPLAN, 确保他们适当地使用相关索引。为此,您需要真实或真实的数据。 (优化数据库查询确实是一个单独的问题;在这个问题中你甚至没有说你使用的是什么数据库......)

      • 最后,当你实现了你的目标时,停下来。不要在收益递减点或对业务没有影响的点之后继续优化。

      【讨论】:

        猜你喜欢
        • 2013-02-24
        • 1970-01-01
        • 2018-07-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多