Po wielu badaniach odkryłem, że problem wynika z tego, jak zapytanie jest zbudowane dla pola wyszukiwania administratora (w ChangeList
klasa). W wyszukiwaniu wieloterminowym (słowa oddzielone spacją) każdy termin jest dodawany do QuerySetu poprzez łączenie nowego filter()
. Gdy istnieje jedno lub więcej powiązanych pól w search_fields
, utworzone zapytanie SQL będzie miało dużo JOIN
połączone jeden po drugim z wieloma JOIN
dla każdego powiązanego pola (zobacz moje powiązane pytanie
kilka przykładów i więcej informacji). Ten łańcuch JOIN
jest tak, że każdy termin będzie wyszukiwany tylko w podzbiorze filtru danych według terminu poprzedzającego ORAZ, co najważniejsze, aby powiązane pole musiało zawierać tylko jeden termin (w przeciwieństwie do WSZYSTKICH terminów), aby uzyskać dopasowanie. Zobacz Rozpiętość relacji wielowartościowych w dokumentacji Django, aby uzyskać więcej informacji na ten temat. Jestem prawie pewien, że jest to zachowanie pożądane przez większość czasu w polu wyszukiwania administratora.
Wadą tego zapytania (z powiązanymi polami) jest to, że różnice w wydajności (czas na wykonanie zapytania) mogą być naprawdę duże. Zależy to od wielu czynników:liczby wyszukiwanych terminów, wyszukiwanych terminów, rodzaju wyszukiwania pól (VARCHAR itp.), liczby wyszukiwanych pól, danych w tabelach, wielkości tabel itp. Z odpowiednią kombinacją jest to łatwe mieć zapytanie, które trwa w większości w nieskończoność (zapytanie, które zajmuje mi więcej niż 10 minut, jest zapytaniem, które trwa w nieskończoność w kontekście tego pola wyszukiwania).
Powodem, dla którego może to trwać tak długo, jest to, że baza danych musi utworzyć tabelę tymczasową dla każdego terminu i przeskanować ją głównie w celu wyszukania następnego terminu. Tak więc sumuje się to bardzo szybko.
Możliwa zmiana do zrobienia w celu poprawy wydajności to ORAZ wszystkie terminy w tym samym filter()
. W ten sposób ich będzie tylko jedno JOIN
według powiązanego pola (lub 2, jeśli jest wiele do wielu) zamiast o wiele więcej. To zapytanie będzie dużo szybsze i będzie miało naprawdę małą zmienność wydajności. Wadą jest to, że powiązane pola będą musiały zawierać WSZYSTKIE terminy do dopasowania, więc w wielu przypadkach możesz uzyskać mniej dopasowań.
AKTUALIZACJA
Zapytany przez trinchet oto, co jest potrzebne do zmiany zachowania wyszukiwania (dla Django 1.7). Musisz zastąpić get_search_results()
klas administracyjnych, w których chcesz tego rodzaju wyszukiwania. Musisz skopiować cały kod metody z klasy bazowej (ModelAdmin
) do własnej klasy. Następnie musisz zmienić te linie:
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))
Do tego:
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))
Ten kod nie jest testowany. Mój oryginalny kod był dla Django 1.4 i właśnie zaadaptowałem go tutaj dla wersji 1.7.