在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')
...
我的问题是当我从作者管理列表页面搜索2个或更多短项时,MySQL开始花费大量时间(对于3个术语查询至少8秒)。我有大约5000名作者和2500本书。这里的短非常重要。如果我搜索'a b c',那么3个非常短的术语,我没有足够的耐心等待结果(我等了至少2分钟)。相反,如果我搜索“所有蜜蜂线索”,我在2秒内得到了结果。所以问题看起来确实是相关领域的短期。
此搜索产生的SQL查询有很多JOIN,LIKE,AND和OR但没有子查询。
我正在使用MySQL 5.1,但我尝试使用5.5而没有更多成功。
我还尝试将innodb_buffer_pool_size
增加到一个非常大的值。没有改变。
我现在唯一想要改进性能的想法是将isbn
和title
字段反规范化(即将它们直接复制到作者中)但我必须添加一堆机制来保持这些字段与Book中的真实字段同步。
有关如何改进此查询的任何建议?
答案 0 :(得分:8)
经过大量调查后,我发现问题来自于如何为管理搜索字段构建搜索查询(在ChangeList
类中)。在多项搜索(由空格分隔的单词)中,通过链接新的filter()
将每个术语添加到QuerySet中。当search_fields
中有一个或多个相关字段时,创建的SQL查询会有很多JOIN
一个接一个地链接,每个相关字段有很多JOIN
(请参阅我的{{ 3}}一些例子和更多信息)。这个JOIN
链是这样的,因此每个术语只能在先前术语AND中搜索数据过滤器的子集,最重要的是,相关字段只需要有一个术语(vs需要拥有所有术语) )做一场比赛。有关此主题的更多信息,请参阅Django文档中的related question。我很确定这是管理搜索领域大部分时间都需要的行为。
此查询的缺点(涉及相关字段)是性能的变化(执行查询的时间)可能非常大。这取决于很多因素:搜索的术语数量,搜索的术语,字段搜索的种类(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。
答案 1 :(得分:1)
您可以为ModelAdmin子类重新定义get_changelist,并尝试手动优化查询there。例如,可以使用完全匹配而不是icontains来查找ISBN,并且可以在Book上添加子查询以更快地工作。