如何提高Django管理搜索相关领域的查询性能(MySQL)

Eti*_*nne 4 mysql django django-admin

在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 ...
Run Code Online (Sandbox Code Playgroud)

admin.py

class AuthorAdmin(admin.ModelAdmin):
    search_fields = ('first_name', 'last_name', 'books__isbn', 'books__title')

    ...
Run Code Online (Sandbox Code Playgroud)

我的问题是当我从作者管理列表页面搜索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增加到一个非常大的值.没有改变.

我现在唯一想要改进性能的想法是对文本进行非规范化isbntitle字段化(即将它们直接复制到作者中),但我必须添加一些机制来使这些字段与Book中的实际字段保持同步.

有关如何改进此查询的任何建议?

Eti*_*nne 8

经过大量调查后,我发现问题来自于如何为管理搜索字段(在ChangeList类中)构建搜索查询.在多项搜索(由空格分隔的单词)中,通过链接新项将每个项添加到QuerySet filter().如果有一个或多个相关字段search_fields,则创建的SQL查询将JOIN依次链接很多JOIN,每个相关字段有很多(请参阅我的相关问题以获取一些示例和更多信息).这个链JOIN是这样的,所以每个术语只能在先前术语AND中搜索数据过滤器的子集,最重要的是,相关的字段只需要有一个术语(vs需要有所有术语)来进行匹配.有关此主题的更多信息,请参阅Django文档中的跨越多值关系.我很确定这是管理搜索领域大部分时间都需要的行为.

此查询(涉及相关字段)的缺点是性能的变化(执行查询的时间)可能非常大.这取决于很多因素:搜索的术语数量,搜索的术语,字段搜索的种类(VARCHAR等),字段搜索的数量,表格中的数据,表格的大小等.正确的组合很容易有一个大部分将永远占用的查询(对我来说,查询花费超过10分钟.这是一个永远在此搜索字段的上下文中的查询).

可能需要这么长时间的原因是数据库需要为每个术语创建一个临时表,并且主要扫描它以完全搜索下一个术语.所以,这很快就会增加.

为改善性能而做的可能的改变是将所有术语与AND相同filter().这样他们将只有一个JOIN相关领域(如果它是多对多,则为2)而不是更多.此查询将更快,并且性能变化非常小.缺点是相关字段必须具有匹配的所有项,因此,在许多情况下,您可以获得较少的匹配.

UPDATE

正如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))
Run Code Online (Sandbox Code Playgroud)

为此:

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))
Run Code Online (Sandbox Code Playgroud)

此代码未经过测试.我的原始代码是针对Django 1.4的,我只是在这里修改它为1.7.