[英]How to improved query performance in Django admin search on related fields (MySQL)
在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本书。 这里的简短非常重要。 如果我搜索'ab c',那么3个非常短的术语,我没有足够的耐心等待结果(我等了至少2分钟)。 相反,如果我搜索“所有蜜蜂线索”,我在2秒内得到了结果。 所以这个问题看起来确实与相关领域的短期相关。
此搜索产生的SQL查询有很多JOIN,LIKE,AND和OR但没有子查询。
我正在使用MySQL 5.1,但我尝试使用5.5而没有更多的成功。
我还尝试将innodb_buffer_pool_size
增加到一个非常大的值。 没有改变。
我现在唯一想要提高性能的想法是对isbn
和title
字段进行非规范化(即将它们直接复制到作者中)但我必须添加一些机制来保持这些字段与Book中的真实字段保持同步。
有关如何改进此查询的任何建议?
经过大量调查后,我发现问题来自于如何为管理搜索字段(在ChangeList
类中)构建搜索查询。 在多项搜索(由空格分隔的单词)中,通过链接新的filter()
将每个项添加到QuerySet。 当search_fields
有一个或多个相关字段时,创建的SQL查询将JOIN
链接很多JOIN
,每个相关字段有很多JOIN
( 有关一些示例和更多信息,请参阅我的相关问题 )。 这个JOIN
链是存在的,因此每个术语只能在先前术语AND的数据过滤器子集中进行搜索,最重要的是,相关字段只需要有一个术语(需要有所有术语)来制作一个术语比赛。 有关此主题的更多信息,请参阅Django文档中的跨越多值关系 。 我很确定这是管理搜索领域大部分时间都需要的行为。
此查询(涉及相关字段)的缺点是性能的变化(执行查询的时间)可能非常大。 这取决于很多因素:搜索的术语数量,搜索的术语,字段搜索的种类(VARCHAR等),字段搜索的数量,表格中的数据,表格的大小等。正确的组合很容易有一个大部分将永远占用的查询(对我来说,查询花费超过10分钟。这是一个永远在此搜索字段的上下文中的查询)。
可能需要这么长时间的原因是数据库需要为每个术语创建一个临时表,并且主要扫描它以完全搜索下一个术语。 所以,这很快就会增加。
要改进性能的可能做法是在同一个filter()
中对所有术语进行AND运算 。 通过这种方式,他们将只有一个相关领域的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。
您可以为ModelAdmin子类重新定义get_changelist,并尝试在那里手动优化查询。 例如,可以使用完全匹配而不是icontains来查找ISBN,并且可以在Book上添加子查询以更快地工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.