【问题标题】:django - improve performance of __in queryset in M2M filteringdjango - 提高 M2M 过滤中 __in 查询集的性能
【发布时间】:2021-07-03 15:02:17
【问题描述】:

我有一个模型与另一个模型具有 M2M 关系。

这些是我的模型:

class Catalogue(models.Model):
    city = models.CharField(db_index=True,max_length=100, null=True)
    district = models.CharField(db_index=True,max_length=100, null=True)
    type = models.ManyToManyField(Type, db_index=True)
    datetime = models.CharField(db_index=True, max_length=100, null=True)


class Type(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

这是views.py:

class all_ads(generic.ListView):
    paginate_by = 12
    template_name = 'new_list_view_grid-card.html'
    def get_queryset(self):
        city_district = self.request.GET.getlist('city_district')
        usage = self.request.GET.get('usage')
        status = self.request.GET.get('status')

        last2week = datetime.datetime.now() - datetime.timedelta(days=14)

        status = status.split(',')

        if usage:
            usage = usage.split(',')
        else:
            usage = ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31']

        intersections = list(set(status).intersection(usage))
        type_q = (Q(type__in=intersections) & Q(type__isnull=False))

        result = models.Catalogue.objects.filter(
            Q(datetime__gte=last2week) &
            type_q &
            ((reduce(operator.or_, (Q(city__contains=x) for x in city_district)) & Q(city__isnull=False)) |
             (reduce(operator.or_, (Q(district__contains=x) for x in city_district)) & Q(district__isnull=False)))
        ).distinct().order_by('-datetime').prefetch_related('type')

        return result

我想用一些查询过滤 MySQL 数据库并在列表视图中返回结果。

它在小型数据库上运行良好,但对于大型数据库,返回结果需要 10 多秒。如果我删除type_q查询,需要2秒(减少10秒!)。

如何提高__in 查询集的性能?

【问题讨论】:

  • 如果您将Q(type__in=intersections) 用于type_q 会怎样?
  • @WillemVanOnsem 效果不大……需要10秒
  • 可能不是type_q 是问题所在,而是type_q 充当“乘数”,因此city__containsdetrict__contains 现在需要枚举相同的记录多次。
  • 如果您没有索引,您可能还需要在该datetime 字段上创建索引(您正在使用order_by)。
  • 如果您先查询cids = Catalogue.objects.filter((reducue__...).values('pk'),然后使用.filter(Q(datetime__gte=last2week) & type_q & pk__int=cids,该怎么办?

标签: django django-queryset


【解决方案1】:

看起来type_q 本身并不是真正的罪魁祸首,而是作为一个乘数,因为现在我们创建了一个LEFT OUTER JOIN,因此__contains 运行在所有组合上。因此,这更像是两个过滤器一起工作的特性

我们可以省略这个:

cat_ids = list(Catalogue.objects.filter(
    Q(*[Q(city__contains=x) for x in city_district], _connector=Q.OR) |
    Q(*[Q(district__contains=x) for x in city_district], _connector=Q.OR)
).values_list('pk', flat=True))

result = models.Catalogue.objects.filter(
    Q(datetime__gte=last2week),
    type_q,
    pk__in=cat_ids
).distinct().order_by('-datetime').prefetch_related('type')

某些数据库(众所周知,MySQL不会很好地优化子查询),甚至可以通过子查询来做到这一点。所以这里我们没有具体化列表,而是让 Django 使用子查询:

cat_ids = Catalogue.objects.filter(
    Q(*[Q(city__contains=x) for x in city_district], _connector=Q.OR) |
    Q(*[Q(district__contains=x) for x in city_district], _connector=Q.OR)
).values_list('pk', flat=True)

result = models.Catalogue.objects.filter(
    Q(datetime__gte=last2week),
    type_q,
    pk__in=cat_ids
).distinct().order_by('-datetime').prefetch_related('type')

【讨论】:

  • 嘿,你的这个回答让我想起了我昨天在 meta 上看到了这个post(顺便说一句,你在里面提到了;))。似乎语法突出显示不再适用于内联 HTML(我可以看到您在尝试编辑时使用了 <strong>,但它没有出现在呈现的帖子中。)
  • 谢谢! ...现在时间是 6 秒! ...但仍然太长...我认为这是关于分页...我猜 django 在每个页面上加载所有数据库...因为使用更大的数据库,它需要更多时间
  • @omidjahadi:您是使用list(...) 部分还是使用子查询?
  • @omidjahadi:我认为分页对于所有增长的数据库最终都是必要的,因为最终数据将太大而无法有效处理。
  • @AbdulAzizBarkat:谢谢,我不知道。我认为我看到的第一个使用它的用户是mat。我发现它很有帮助,所以也开始使用它:)。
猜你喜欢
  • 2015-12-30
  • 1970-01-01
  • 2018-10-20
  • 2021-10-20
  • 2017-02-13
  • 2020-09-14
  • 2014-03-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多