如何提高 Django 管理员搜索相关字段(MySQL)中的查询性能
Posted
技术标签:
【中文标题】如何提高 Django 管理员搜索相关字段(MySQL)中的查询性能【英文标题】:How to improved query performance in Django admin search on related fields (MySQL) 【发布时间】:2012-03-29 00:20:24 【问题描述】:在 Django 中我有这个:
models.py
class Book(models.Model):
isbn = models.CharField(max_length=16, db_index=True)
title = models.CharField(max_length=255, db_index=True)
... other fields ...
class Author(models.Model):
first_name = models.CharField(max_length=128, db_index=True)
last_name = models.CharField(max_length=128, db_index=True)
books = models.ManyToManyField(Book, blank=True)
... other fields ...
admin.py
class AuthorAdmin(admin.ModelAdmin):
search_fields = ('first_name', 'last_name', 'books__isbn', 'books__title')
...
我的问题是,当我从 Author admin list 页面使用 2 个或更多短期术语进行搜索时,mysql 开始花费大量时间(对于 3 个术语查询,至少需要 8 秒)。我有大约 5000 位作者和 2500 本书。这里的short很重要。如果我搜索“a b c”,那么 3 个非常短的术语,我没有足够的耐心等待结果(我至少等了 2 分钟)。相反,如果我搜索“所有蜜蜂线索”,我会在 2 秒内得到结果。所以这个问题看起来真的是相关领域的短期问题。
此搜索产生的 SQL 查询有很多 JOIN、LIKE、AND 和 OR,但没有子查询。
我正在使用 MySQL 5.1,但我尝试使用 5.5 没有更多成功。
我还尝试将innodb_buffer_pool_size
增加到一个非常大的值。没有任何改变。
我现在唯一可以提高性能的想法是非规范化为 isbn
和 title
字段(即直接将它们复制到作者中)但我将不得不添加一堆机制来保持这些字段同步与书中真实的。
关于如何改进此查询的任何建议?
【问题讨论】:
【参考方案1】:经过大量调查,我发现问题出在如何为管理员搜索字段构建搜索查询(在ChangeList
类中)。在多词搜索(用空格分隔的词)中,每个词通过链接一个新的filter()
添加到 QuerySet。当search_fields
中有一个或多个相关字段时,创建的SQL查询将有很多JOIN
一个接一个地链接,每个相关字段都有很多JOIN
(有关一些示例和更多信息,请参阅我的related question信息)。这条JOIN
链就在那里,因此每个术语将仅在数据过滤器的子集中通过前一个术语进行搜索,并且最重要的是,相关字段只需要一个术语(而不是需要所有术语)做一个匹配。有关此主题的更多信息,请参阅 Django 文档中的 Spanning multi-valued relationships。我很确定这是管理员搜索字段大部分时间想要的行为。
此查询(涉及相关字段)的缺点是性能(执行查询的时间)的变化可能非常大。这取决于很多因素:搜索词的数量、搜索的词、字段搜索的类型(VARCHAR 等)、字段搜索的数量、表中的数据、表的大小等。使用正确的组合很容易有一个几乎永远需要的查询(一个需要超过 10 分钟的查询。对我来说是一个在这个搜索字段的上下文中需要永远的查询)。
之所以需要这么长时间,是因为数据库需要为每个词条创建一个临时表,并且大部分时间都对其进行扫描以搜索下一个词条。所以,这加起来真的很快。
提高性能的一个可能更改是ANDed同一filter()
中的所有术语。这样,他们将只有一个 JOIN
相关字段(如果是多对多,则为 2 个)而不是更多。这个查询会快很多,而且性能变化很小。缺点是相关字段必须包含所有要匹配的术语,因此在许多情况下您可以获得较少的匹配项。
更新
正如 trinchet 所要求的,这是更改搜索行为所需的内容(对于 Django 1.7)。您需要覆盖您想要进行此类搜索的管理类的get_search_results()
。您需要将基类 (ModelAdmin
) 中的所有方法代码复制到您自己的类中。然后你需要改变这些行:
for bit in search_term.split():
or_queries = [models.Q(**orm_lookup: bit)
for orm_lookup in orm_lookups]
queryset = queryset.filter(reduce(operator.or_, or_queries))
至于:
and_queries = []
for bit in search_term.split():
or_queries = [models.Q(**orm_lookup: bit)
for orm_lookup in orm_lookups]
and_queries.append(Q(reduce(operator.or_, or_queries)))
queryset = queryset.filter(reduce(operator.and_, and_queries))
此代码未经测试。我的原始代码是针对 Django 1.4 的,我只是在此处将其改编为 1.7。
【讨论】:
嗨@Etienne,可以在这里发布你是如何解决这个问题的吗?我的意思是,你是如何在同一个过滤器中引入所有术语的?谢谢! 感谢@Etienne,我非常感谢。这是 Django ***.com/questions/14426692/… 的另一个很好的解决方案 @trinchet 这个其他解决方案没有做同样的事情。它强制 Django 对整个字符串进行搜索,而不是搜索每个术语(在它们之间使用 OR)。它绝对可以加快查询速度,但需要做出另一种妥协。因此,选择最佳折衷方案取决于每个用例。 你是对的,这不会得到相同的结果,我只是发布了另一种解决方案来覆盖默认的 django 搜索查询。感谢您的意见。【参考方案2】:您可以为 ModelAdmin 子类重新定义 get_changelist 并尝试手动优化查询there。例如,可以使用完全匹配而不是图标来查找 ISBN,并且您可以在 Book 上添加子查询以更快地工作。
【讨论】:
以上是关于如何提高 Django 管理员搜索相关字段(MySQL)中的查询性能的主要内容,如果未能解决你的问题,请参考以下文章