Na veel onderzoek ontdekte ik dat het probleem voortkomt uit hoe de zoekopdracht is gebouwd voor het beheerderszoekveld (in de ChangeList
klas). Bij een zoekopdracht met meerdere termen (woorden gescheiden door spatie) wordt elke term aan de QuerySet toegevoegd door een nieuw filter()
aaneen te koppelen . Wanneer er een of meer gerelateerde velden zijn in de search_fields
, zal de gemaakte SQL-query veel JOIN
. bevatten achter elkaar geketend met veel JOIN
voor elk gerelateerd veld (zie mijn gerelateerde vraag
voor enkele voorbeelden en meer info). Deze keten van JOIN
is er zo dat elke term alleen wordt gezocht in de subset van gegevensfilter op de voorafgaande term EN, het belangrijkste, dat een gerelateerd veld slechts één term hoeft te hebben (in plaats van ALLE termen te hebben) om een overeenkomst te maken. Zie Overkoepelende relaties met meerdere waarden in de Django-documenten voor meer informatie over dit onderwerp. Ik ben er vrij zeker van dat dit het gedrag is dat het meest gewenst is voor het beheerderszoekveld.
Het nadeel van deze query (met gerelateerde velden) is dat de variatie in prestaties (tijd om de query uit te voeren) erg groot kan zijn. Het hangt van veel factoren af:aantal gezochte termen, gezochte termen, soort veldzoekopdracht (VARCHAR, etc.), aantal veldzoekopdrachten, gegevens in de tabellen, grootte van de tabellen, etc. Met de juiste combinatie is het eenvoudig om een zoekopdracht te hebben die meestal een eeuwigheid duurt (een zoekopdracht die meer dan 10 minuten duurt, is voor mij een zoekopdracht die een eeuwigheid duurt in de context van dit zoekveld).
De reden waarom het zo lang kan duren, is dat de database voor elke term een tijdelijke tabel moet maken en deze grotendeels volledig moet scannen om naar de volgende term te zoeken. Dit loopt dus heel snel op.
Een mogelijke verandering om de prestaties te verbeteren is om ANDed alle termen in hetzelfde filter()
. Op deze manier is het maar één JOIN
per gerelateerd veld (of 2 als het veel op veel is) in plaats van veel meer. Deze query zal een stuk sneller zijn en met een zeer kleine prestatievariatie. Het nadeel is dat gerelateerde velden ALLE termen moeten bevatten die overeenkomen, dus u kunt in veel gevallen minder overeenkomsten krijgen.
UPDATE
Zoals gevraagd door trinchet dit is wat er nodig is om het zoekgedrag te veranderen (voor Django 1.7). U moet de get_search_results()
. overschrijven van de beheerdersklassen waar u dit soort zoekopdrachten wilt. U moet alle methodecode van de basisklasse kopiëren (ModelAdmin
) naar je eigen klas. Dan moet je die regels veranderen:
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))
Daarop:
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))
Deze code is niet getest. Mijn originele code was voor Django 1.4 en ik pas het hier gewoon aan voor 1.7.