Efter en masse undersøgelser fandt jeg ud af, at problemet kom fra, hvordan søgeforespørgslen er bygget til admin-søgefeltet (i ChangeList
klasse). I en søgning med flere termer (ord adskilt af mellemrum) føjes hvert udtryk til QuerySet ved at sammenkæde et nyt filter()
. Når der er et eller flere relaterede felter i search_fields
, vil den oprettede SQL-forespørgsel have en masse JOIN
lænket efter hinanden med mange JOIN
for hvert relateret felt (se min relateret spørgsmål
for nogle eksempler og mere info). Denne kæde af JOIN
er der således, at hvert udtryk kun søges i delmængden af datafilteret efter det foregående udtryk OG, vigtigst af alt, at et relateret felt kun behøver at have én term (i forhold til at skulle have ALLE termer) for at kunne matche. Se Omspændende relationer med flere værdier i Django-dokumenterne for mere information om dette emne. Jeg er ret sikker på, at det er den adfærd, der oftest er ønsket for admin-søgefeltet.
Ulempen ved denne forespørgsel (med relaterede felter involveret) er, at variationen i ydeevne (tid til at udføre forespørgslen) kan være virkelig stor. Det afhænger af mange faktorer:antal søgte termer, søgeord, type feltsøgning (VARCHAR osv.), antal feltsøgninger, data i tabellerne, tabellernes størrelse osv. Med den rigtige kombination er det nemt at have en forespørgsel, der for det meste vil tage evigheder (en forespørgsel, der tager mere end 10 min. for mig, er en forespørgsel, der tager evigheder i forbindelse med dette søgefelt).
Grunden til, at det kan tage så lang tid, er, at databasen skal oprette en midlertidig tabel for hver term og scanne den for det meste for at søge efter den næste term. Så det lægger sig virkelig hurtigt sammen.
En mulig ændring for at forbedre ydeevnen er at ANDed alle termer i samme filter()
. På denne måde vil de kun være én JOIN
efter relateret felt (eller 2, hvis det er mange til mange) i stedet for mange flere. Denne forespørgsel vil være meget hurtigere og med meget lille ydeevnevariation. Ulempen er, at relaterede felter skal have ALLE vilkårene for at matche, så du kan få færre matches i mange tilfælde.
OPDATERING
Som spurgt af trinchet her er hvad der er nødvendigt for at ændre søgeadfærden (for Django 1.7). Du skal tilsidesætte get_search_results()
af de admin-klasser, hvor du ønsker denne form for søgning. Du skal kopiere al metodekoden fra basisklassen (ModelAdmin
) til din egen klasse. Så skal du ændre disse linjer:
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))
Til det:
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))
Denne kode er ikke testet. Min originale kode var til Django 1.4, og jeg tilpasser den bare til 1.7 her.