Efter många undersökningar upptäckte jag att problemet kom från hur sökfrågan är byggd för admin-sökfältet (i ChangeList
klass). I en sökning med flera termer (ord separerade med mellanslag) läggs varje term till i QuerySet genom att kedja ett nytt filter()
. När det finns ett eller flera relaterade fält i search_fields
, kommer den skapade SQL-frågan att ha många JOIN
kedjade efter varandra med många JOIN
för varje relaterat fält (se min relaterad fråga
för några exempel och mer information). Denna kedja av JOIN
är det så att varje term endast kommer att sökas i delmängden av datafiltret med den föregående termen OCH, viktigast av allt, att ett relaterat fält bara behöver ha en term (mot att behöva ha ALLA termer) för att göra en matchning. Se Spännande relationer med flera värden i Django-dokumenten för mer information om detta ämne. Jag är ganska säker på att det är det beteende som för det mesta önskas för admin-sökfältet.
Nackdelen med denna fråga (med relaterade fält inblandade) är att variationen i prestanda (tid för att utföra frågan) kan vara riktigt stor. Det beror på många faktorer:antal sökord, sökta termer, typ av fältsökning (VARCHAR, etc.), antal fältsökningar, data i tabellerna, storlek på tabellerna, etc. Med rätt kombination är det enkelt att ha en fråga som kommer att ta för evigt (en fråga som tar mer än 10 minuter för mig är en fråga som tar evigheter i sammanhanget med det här sökfältet).
Anledningen till att det kan ta så lång tid är att databasen behöver skapa en tillfällig tabell för varje term och skanna den mestadels helt för att söka efter nästa term. Så det här lägger sig väldigt snabbt.
En möjlig ändring att göra för att förbättra prestandan är att ANDed alla termer i samma filter()
. På så sätt blir de bara en JOIN
efter relaterat fält (eller 2 om det är många till många) istället för många fler. Den här frågan kommer att vara mycket snabbare och med mycket liten prestandavariation. Nackdelen är att relaterade fält måste ha ALLA termer för att matcha, så du kan få färre matchningar i många fall.
UPPDATERA
Som frågat av trinchet här är vad som behövs för att ändra sökbeteendet (för Django 1.7). Du måste åsidosätta get_search_results()
av de administratörsklasser där du vill ha den här typen av sökning. Du måste kopiera all metodkod från basklassen (ModelAdmin
) till din egen klass. Sedan måste du ändra dessa rader:
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))
Till 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))
Denna kod är inte testad. Min ursprungliga kod var för Django 1.4 och jag anpassar den bara för 1.7 här.