Как улучшить производительность запросов в поиске администратора Django по смежным полям (MySQL) - PullRequest
4 голосов
/ 12 марта 2012

В 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')

    ...

Моя проблема в том, что, когда я выполняю поиск на странице списка администраторов с двумя или более короткими терминами, MySQL начинает занимать много времени (по крайней мере, 8 секунд для запроса с тремя терминами).У меня около 5000 авторов и 2500 книг. short здесь очень важно.Если я ищу «ab c», то есть 3 действительно коротких термина, мне не хватит терпения ждать результата (я ждал не менее 2 минут).Вместо этого, если я ищу «все подсказки пчелы», я получаю результат через 2 секунды.Таким образом, проблема выглядит очень кратко по связанным полям.

SQL-запрос, полученный в результате этого поиска, содержит много JOIN, LIKE, AND и OR, но без подзапроса.

I 'Я использую MySQL 5.1, но я пытался с 5.5 безуспешно.

Я также пытался увеличить innodb_buffer_pool_size до действительно большого значения.Это ничего не меняет.

Единственная идея, которую я имею сейчас, чтобы улучшить производительность, - это денормализовать в isbn и title поле (то есть скопировать их непосредственно в авторов), но мне нужно будет добавить несколькоМеханика для синхронизации этих полей с реальными в Книге.

Есть предложения по улучшению этого запроса?

Ответы [ 2 ]

8 голосов
/ 16 марта 2012

После многих исследований я обнаружил, что проблема заключается в том, как устроен поисковый запрос для поля поиска администратора (в классе ChangeList).При поиске по нескольким терминам (слова, разделенные пробелом) каждый термин добавляется в QuerySet путем создания цепочки нового filter().Когда в search_fields есть одно или несколько связанных полей, созданный SQL-запрос будет иметь множество JOIN цепочек один за другим со многими JOIN для каждого связанного поля (см. Мой связанный вопрос для некоторых примеров и дополнительной информации).Эта цепочка JOIN существует так, что каждый термин будет искать только в подмножестве фильтра данных по предшествующему термину И, что наиболее важно, чтобы в связанном поле был только один термин (против необходимости иметь ВСЕ термины) длясделать матч.См. Охват многозначных отношений в Django документах для получения дополнительной информации по этому вопросу.Я почти уверен, что такое поведение требуется в большинстве случаев для поля поиска администратора.

Недостаток этого запроса (со связанными полями) состоит в том, что изменение производительности (время выполнения запроса) можетбыть действительно большим.Это зависит от множества факторов: количества искомых терминов, искомых терминов, вида поиска по полю (VARCHAR и т. Д.), Количества поиска по полю, данных в таблицах, размера таблиц и т. Д. При правильной комбинации это легкоиметь запрос, который будет длиться вечно (запрос, который для меня занимает более 10 минут, является запросом, который занимает бесконечно в контексте этого поля поиска).

Причина, по которой он может занимать так много временизаключается в том, что базе данных необходимо создать временную таблицу для каждого термина и сканировать ее в основном целиком для поиска следующего термина.Итак, это складывается очень быстро.

Возможное изменение для улучшения производительности - это И все термины в одном и том же 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 голос
/ 13 марта 2012

Вы можете переопределить get_changelist для подкласса ModelAdmin и попытаться оптимизировать запрос вручную там . Например, ISBN можно искать с точным соответствием вместо значков, и вы можете добавлять подзапросы в Book для более быстрой работы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...