【问题标题】:Django Filtering Query slow on Large row dataset大行数据集上的 Django 过滤查询慢
【发布时间】:2020-01-14 05:43:30
【问题描述】:
  • 我的站点在从以下字段数据中过滤大行时遇到性能缓慢的问题。我在字段中存储的数据类型是body 字段中的RAW HTTP Response Body,其中包含大量内容,Project 以下模型有 32k 个对象。
class Project(models.Model):
    body = models.TextField(null=True,blank=True)
  • body 执行过滤需要几秒钟。
>>> Project.objects.filter(body__icontains='x').count() ### 5-6 seconds
20472

我使用 Django 分页器对每个页面的数据进行切片。

>>> from django.core.paginator import Paginator
>>> data = Project.objects.filter(body__icontains='x')
>>> p = Paginator(data,10)
>>> p.page(1) ### takes 5-6 seconds here again because this function counts the total number of pages based on data.count()
<Page 1 of 2048>
  • 然后我将分页器数据传递给模板,该模板只是循环并显示正文。
{% for each_data in data %}
    {{ each_data.body }}
{% endfor %}
  • 分页器的每一页都需要等待 5-6 秒,因为分页器函数对总数据集执行计数并返回页面范围。这是最大的缺点。

使用普通 Python 切片:

>>> data = Project.objects.filter(body__icontains='x')
>>> for each_data in data[0:10]:  ### 0.1 seconds
        print(each_data.body)
  • 如您所见,如果我们根本不访问数据库,只是对数据进行切片并将其传递给模板,那么它将立即显示数据。

  • 我相信当Paginator 函数计算我们每页过滤结果的总页数时,查询会变慢。
  • 我相信分页器不适合大型数据集。

使用切片功能的缺点是:

  • 我们无法计算过滤器返回的结果对象的总数。
  • 看不到分页器应该有的页数,因为我们没有计算总数据集。

有没有什么办法可以解决这个问题,以便在分页的同时获得更快的结果?

【问题讨论】:

  • 如果你想改进分页,你可以按照这个教程:medium.com/@hakibenita/…
  • 我已经阅读了这篇文章,我相信我们可以不用分页本身,因为我们不计算返回的过滤对象总数,可以简单地使用 [offset:limit] 来完成,但是,我正在寻找不损害count on returned objectsperformance 的解决方案
  • 请注意,您的第二个示例使用了覆盖的QuerySet.__getitem__() 实现,它不会在之后对结果集进行切片,而是在生成的 SQL 查询中添加“offset”和“limit”子句。
  • 虽然分页器发出的count 查询确实是开销的一部分,但根本问题是文本字段上的ilike %x% 条件。您可能需要查看您的 RDBMS 手册以了解如何优化这一点(当然,如果可能的话)。

标签: django django-models


【解决方案1】:

为什么会这样?

考虑这个例子:

Project.objects.filter(body__icontains='x').count()

这将被翻译成这个查询(PostgreSQL):

SELECT COUNT(*) AS res
  FROM project
 WHERE UPPER(project.body::text) LIKE UPPER('%x%')

如果有很多记录,它会很慢。 @bruno 建议的方式 - 尝试找到优化它的方法(这可能取决于您的 RDBMS)。

Django 中的经典分页通过限制和偏移 SQL 语句实现。结果:

COUNT -(执行时间:10.7s)

OFFSET X - DB 需要经过适当排序后从磁盘中读取 X 条记录;如果已将新元素插入到已请求的页面中,则元素列表无效。

通常,计数是散列的,当添加新记录时,我们只是进行更新。 OFFSET 很难优化(缓存?)。

有更好的解决方案吗?

KEYSET分页,算法如下:

  • 我们记住第一个 FIRST_TIMESTAMP 和最后一个的标识符 页面上的LAST_TIMESTAMP 元素,例如,这可能是发布日期。
  • 要获取下一页我们正在使用构造:
WHERE table.date > LAST_TIMESTAMP ORDER_BY date ASC LIMIT <PAGE SIZE> 

优点:

  • 在大型查询集上工作得更快。
  • 在添加新页面时正确处理对先前页面的更改 元素。 Django 有几个implementations

缺点:

  • 无法切换到随机页面,没有预加载数据,这就是为什么 仅适用于无限滚动。

因此,如果是您的情况,您可以显着加快分页速度。

【讨论】:

  • 我很难想出在这种情况下实现infinite_scroll_pagination,您能否提供一个适合我的问题的简单示例。
猜你喜欢
  • 2017-07-28
  • 2012-05-15
  • 2012-02-28
  • 2020-08-22
  • 2019-04-15
  • 2011-09-29
相关资源
最近更新 更多