Dopo molte indagini ho scoperto che il problema deriva da come viene costruita la query di ricerca per il campo di ricerca dell'amministratore (nel ChangeList
classe). In una ricerca multi-termine (parole separate da spazio) ogni termine viene aggiunto al QuerySet concatenando un nuovo filter()
. Quando sono presenti uno o più campi correlati nei search_fields
, la query SQL creata avrà molti JOIN
incatenati uno dopo l'altro con molti JOIN
per ogni campo correlato (vedi il mio domanda correlata
per alcuni esempi e maggiori informazioni). Questa catena di JOIN
è presente in modo che ogni termine venga cercato solo nel sottoinsieme del filtro di dati in base al termine precedente E, cosa più importante, che un campo correlato deve avere solo un termine (rispetto alla necessità di avere TUTTI i termini) per creare una corrispondenza. Vedi Comprendere relazioni multivalore nei documenti Django per maggiori informazioni su questo argomento. Sono abbastanza sicuro che sia il comportamento desiderato per la maggior parte del tempo per il campo di ricerca dell'amministratore.
Lo svantaggio di questa query (con i relativi campi coinvolti) è che la variazione delle prestazioni (tempo per eseguire la query) può essere molto ampia. Dipende da molti fattori:numero di termini ricercati, termini ricercati, tipo di ricerca del campo (VARCHAR, ecc.), numero di ricerca del campo, dati nelle tabelle, dimensione delle tabelle, ecc. Con la giusta combinazione è facile avere una query che richiederà per lo più un'eternità (una query che richiede più di 10 minuti per me è una query che richiede un'eternità nel contesto di questo campo di ricerca).
Il motivo per cui può richiedere così tanto tempo è che il database deve creare una tabella temporanea per ogni termine e scansionarlo principalmente interamente per cercare il termine successivo. Quindi, questo si somma molto rapidamente.
Una possibile modifica da fare per migliorare le prestazioni è ANDed tutti i termini nello stesso filter()
. In questo modo il loro sarà solo un JOIN
per campo correlato (o 2 se è da molti a molti) invece di molti di più. Questa query sarà molto più veloce e con variazioni di prestazioni davvero ridotte. Lo svantaggio è che i campi correlati dovranno avere TUTTI i termini da abbinare, quindi in molti casi puoi ottenere meno corrispondenze.
AGGIORNAMENTO
Come chiesto da trinchet ecco cosa è necessario per modificare il comportamento di ricerca (per Django 1.7). Devi sovrascrivere get_search_results()
delle classi di amministrazione in cui desideri questo tipo di ricerca. Devi copiare tutto il codice del metodo dalla classe base (ModelAdmin
) alla tua classe. Quindi devi cambiare quelle righe:
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))
A quello:
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))
Questo codice non è testato. Il mio codice originale era per Django 1.4 e l'ho appena adattato per 1.7 qui.