在 Django 视图中保持状态以提高分页性能
Posted
技术标签:
【中文标题】在 Django 视图中保持状态以提高分页性能【英文标题】:Keeping state in a Django view to improve performance for pagination 【发布时间】:2016-01-02 18:01:40 【问题描述】:我正在设计一个data-tables
驱动的 Django 应用程序,并有一个 API 视图,data-tables
使用 AJAX 调用(我在其服务器端处理模式中使用 data-tables
)。它实现了搜索、分页和排序。
我的数据库最近变大了(大约 500,000 个条目)并且性能受到很大影响,无论是搜索还是简单地移动到下一页。我怀疑我编写视图的方式非常低效。这是我在视图中所做的(假设我的数据库中的对象是比萨饼):
filtered = Pizza.objects.filter(...)
获取匹配搜索条件的比萨饼集。 (或Pizza.objects.all()
,如果没有搜索条件)。
paginated = filtered[start: start + length]
仅获取披萨的当前页面。 (最多只有 100 个)。根据用户所在的页面,从data-tables
客户端代码传入开始和长度。
pizzas = paginated.order_by(...)
将排序应用到当前页面。
然后我将pizzas
转换为 JSON 并从视图中返回它们。
看起来,虽然搜索可能是对 500,000 个条目的缓慢操作,但仅仅移动到下一页不应该要求我们重做整个搜索。所以我想做的是在视图中缓存一些东西(这是一个基于类的视图)。我会跟踪最后一个搜索字符串是什么,以及它产生的一组结果。
然后,如果请求通过并且搜索字符串没有不同(如果用户单击几页结果时会发生这种情况),我不必再次访问数据库来获取过滤后的结果-- 我可以只使用缓存的版本。
这是一个只读应用程序,因此不同步不会成为问题。
我什至可以保留一大堆搜索字符串和它们应该生成的比萨饼的字典。
我想知道的是:这是解决问题的合理方法吗?还是我忽略了什么?另外,我在这里重新发明***吗?并不是说这不容易实现,而是QuerySet
上是否有内置选项或其他东西可以做到这一点?
【问题讨论】:
【参考方案1】:pizzas = paginated.order_by(...)
很慢,它会排序所有比萨而不是当前页面。索引帮助:https://docs.djangoproject.com/en/1.8/topics/db/optimization/#use-standard-db-optimization-techniques
如果你真的想要缓存,请查看https://github.com/Suor/django-cacheops,“一个支持自动或手动查询集缓存和自动粒度事件驱动失效的漂亮应用。”
【讨论】:
哇,这是真的吗?如果我先对查询集进行切片,然后对其进行过滤/排序/任何内容,它实际上是否过滤/排序/任何内容然后切片? 根据 django 1.8 的文档,“切片未评估的 QuerySet 会返回另一个未评估的 QuerySet,不允许进一步修改它(例如,添加更多过滤器或修改排序)”。所以看来我部分错了,pizzas = paginated.order_by(...)
是不允许的。还是“考虑为经常使用 filter()、exclude()、order_by() 查询的字段添加索引”【参考方案2】:
有多种方法可以改善你的代码结构,
首先是您使用 Django ORM 命中根据您的页码仅获取所需的数据,其次是您缓存您的 ORM 输出并在再次传递相同的查询时重用该结果。
首先是这样的。
在您的代码中
Pizza.objects.all()
paginated = filtered[start: start + length]
您首先获取所有数据,然后将其切片,这是非常昂贵的 SQL 查询,将其转换为
filtered = Pizza.objects.all()[(page_number-1) * 30, (page_number-1) * 30 + 30]
上面给定的 ORM 只会根据提供的页码获取那些行,并且与获取所有然后切片相比非常快。
第二种方式,是先根据查询取数据放上去,缓存方案如memcache或redis,下次需要从数据库取数据时,先检查如果该查询的缓存中存在数据,如果存在,则只需使用该数据,因为内存缓存解决方案比从数据库中获取数据要快得多,因为内存和硬盘驱动器之间的输入输出传输非常大,我们知道传统上,硬盘驱动器很慢。
【讨论】:
以上是关于在 Django 视图中保持状态以提高分页性能的主要内容,如果未能解决你的问题,请参考以下文章