在 Django 我有这个:

模型.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 增加到一个非常大的值。那什么都改变不了。

我现在提高性能的唯一想法是对 isbntitle 字段进行非规范化(即,将它们直接复制到 Authors 中),但我必须添加一堆机制来使这些字段与 Book 中的真实字段保持同步。

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

最佳答案

经过大量调查,我发现问题来自如何为管理搜索字段(在 ChangeList 类中)构建搜索查询。在多词搜索(用空格分隔的单词)中,每个词都通过链接一个新的 filter() 被添加到 QuerySet 中。当 search_fields 中有一个或多个相关字段时,创建的 SQL 查询将有很多 JOIN 一个接一个链接在一起,每个相关字段都有许多 JOIN(有关一些示例和更多信息,请参阅我的 related question)。这条 JOIN 链在那里,因此每个术语将仅在数据过滤器的子集中按先例术语进行搜索,并且最重要的是,相关字段只需要一个术语(与需要所有术语相比)来进行比赛。有关此主题的更多信息,请参阅 Django 文档中的 Spanning multi-valued relationships。我很确定这是管理员搜索字段大部分时间想要的行为。
此查询(涉及相关字段)的缺点是性能(执行查询的时间)的变化可能非常大。这取决于很多因素:搜索词的数量、搜索的词、字段搜索的类型(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。

10-08 04:19