2016-01-21 15 views
5

Ho ereditato una grande base di codice legacy che viene eseguito in django 1.5 e il mio compito attuale è quello di accelerare una sezione del sito che prende ~ 1min per caricare.Migliorare la velocità di query: SELECT con semplice COME

ho fatto un profilo della app e ottenuto questo:

enter image description here

Il colpevole, in particolare, è la seguente query (messo a nudo per brevità):

SELECT COUNT(*) FROM "entities_entity" WHERE (
    "entities_entity"."date_filed" <= '2016-01-21' AND (
    UPPER("entities_entity"."entity_city_state_zip"::text) LIKE UPPER('%Atherton%') OR 
    UPPER("entities_entity"."entity_city_state_zip"::text) LIKE UPPER('%Berkeley%') OR 
    -- 34 more of these 
    UPPER("entities_entity"."agent_city_state_zip"::text) LIKE UPPER('%Atherton%') OR 
    UPPER("entities_entity"."agent_city_state_zip"::text) LIKE UPPER('%Berkeley%') OR 
    -- 34 more of these 
) 
) 

che consistono sostanzialmente in un grande come query su due campi, entity_city_state_zip e agent_city_state_zip che sono campi character varying(200) | not null.

Che query viene eseguita due volte (!), Prendendo 18814.02ms ogni volta, e ancora una volta si sostituisce la COUNT per un SELECT riprendendo un extra 20216.49 (ho intenzione di memorizzare nella cache il risultato della COUNT)

La spiegare assomiglia a questo:

Aggregate (cost=175867.33..175867.34 rows=1 width=0) (actual time=17841.502..17841.502 rows=1 loops=1) 
    -> Seq Scan on entities_entity (cost=0.00..175858.95 rows=3351 width=0) (actual time=0.849..17818.551 rows=145075 loops=1) 
     Filter: ((date_filed <= '2016-01-21'::date) AND ((upper((entity_city_state_zip)::text) ~~ '%ATHERTON%'::text) OR (upper((entity_city_state_zip)::text) ~~ '%BERKELEY%'::text) (..skipped..) OR (upper((agent_city_state_zip)::text) ~~ '%ATHERTON%'::text) OR (upper((agent_city_state_zip)::text) ~~ '%BERKELEY%'::text) OR (upper((agent_city_state_zip)::text) ~~ '%BURLINGAME%'::text))) 
     Rows Removed by Filter: 310249 
Planning time: 2.110 ms 
Execution time: 17841.944 ms 

ho provato con un indice su entity_city_state_zip e agent_city_state_zip utilizzando variou s combinazioni come:

CREATE INDEX ON entities_entity (upper(entity_city_state_zip)); 
CREATE INDEX ON entities_entity (upper(agent_city_state_zip)); 

o utilizzando varchar_pattern_ops, senza fortuna.

il server utilizza qualcosa di simile:

qs = queryset.filter(Q(entity_city_state_zip__icontains = all_city_list) | 
        Q(agent_city_state_zip__icontains = all_city_list)) 

per generare quella query.

Non so che altro per provare,

Grazie!

+2

'query LIKE', che inizia con' '% ...' 'non userà alcun indice btree (compreso' xxx_pattern_ops'). Questi indici sono stati selezionati solo se il modello corrisponde all'inizio. (per esempio 'col LIKE 'XXX%'' o 'col ~ '^ XXX''). Puoi provare il [modulo '' pg_trgm'] (http://www.postgresql.org/docs/current/static/pgtrgm.html), [che fornisce un indice adatto per te] (http: //dba.stackexchange. com/domande/10694/pattern-matching-con-like-simile-a-o-regolari-espressioni-in-PostgreSQL/10696). (ed è possibile usare 'ilike' invece di' like' & 'lower()'/'upper()' chiamate). – pozs

+0

@pozs Non lo sapevo! Proverò a provare – NicoSantangelo

+0

Vorrei almeno sapere quale effetto stava avendo il 'Seq Scan' e se una scansione dell'indice potesse essere sostituita. Scopri quale effetto 'set enable_seqscan = false' ha sul piano. DB sta scappando da un SSD? –

risposta

1

Penso che il problema in "multipli LIKE" e in UPPER ("entities_entity ...

È possibile utilizzare:

WHERE entities_entity.entity_city_state_zip SIMILAR TO '%Atherton%|%Berkeley%'

O qualcosa di simile:

WHERE entities_entity.entity_city_state_zip LIKE ANY(ARRAY['%Atherton%', '%Berkeley%'])


Redatta

A proposito di query SQL prime in Django:

  1. https://docs.djangoproject.com/es/1.9/topics/db/sql/
  2. How do I execute raw SQL in a django migration

saluti

+0

Non sapevo che 'LIKE' supportasse' ANY' con un array come valore. Il problema che ho è di fare 'django' creare quella query che google un po 'per vedere cosa posso trovare – NicoSantangelo

+0

È postgres supportato) Che dire di Django ... Penso che" query raw "è ciò che vuoi https: //docs.djopoproject.com/es/1.9/topics/db/sql/ e leggi anche questo link http://stackoverflow.com/questions/31698103/how-do-i-execute-raw-sql-in-a -django-migration –

1

ho guardato un corso di Pluralsight che ha affrontato un problema molto simile. Il corso era "Postgres for .NET Developers" e questo era nella sezione "Fun With Simple SQL", "Ricerca full-text".

Per riassumere la loro soluzione, utilizzando il tuo esempio:

creare una nuova colonna nella tabella che rappresenterà la vostra entity_city_state_zip come tsvector:

create table entities_entity (
    date_filed date, 
    entity_city_state_zip text, 
    csz_search tsvector not null -- add this column 
); 

Inizialmente potrebbe essere necessario rendere annullabile, allora compilare i dati e renderli non annullabili.

update entities_entity 
set csz_search = to_tsvector (entity_city_state_zip); 

Successivamente, creare un trigger che farà sì che il nuovo campo da compilare ogni volta che viene aggiunto un record o modificato:

create trigger entities_insert_update 
before insert or update on entities_entity 
for each row execute procedure 
tsvector_update_trigger(csz_search,'pg_catalog.english',entity_city_state_zip); 

le query di ricerca possono ora interrogare sul campo tsvector piuttosto che il città/stato/zip campo:

select * from entities_entity 
where csz_search @@ to_tsquery('Atherton') 

Alcune note di interesse su questo:

  • to_tsquery, nel caso in cui non lo si sia utilizzato è più sofisticato dell'esempio precedente. Permette e le condizioni, le corrispondenze parziali, ecc
  • è anche case-insensitive, quindi non c'è bisogno di fare le upper funzioni che avete nella vostra domanda

Come passo finale, ha messo un indice GIN su il campo tsquery:

create index entities_entity_ix1 on entities_entity 
using gin(csz_search); 

Se ho ben capito il giusto corso, questo dovrebbe rendere la query volare, e sarà superare il problema dell'incapacità di un indice btree di lavorare su una query like '%.

Ecco il piano di spiegare su tale query:

Bitmap Heap Scan on entities_entity (cost=56.16..1204.78 rows=505 width=81) 
    Recheck Cond: (csz_search @@ to_tsquery('Atherton'::text)) 
    -> Bitmap Index Scan on entities_entity_ix1 (cost=0.00..56.04 rows=505 width=0) 
     Index Cond: (csz_search @@ to_tsquery('Atherton'::text)) 
+0

Questo è davvero bello, ci provo appena posso – NicoSantangelo

+0

È davvero fantastico. Ho fatto alcuni test rapidi su circa 2.000.000 di file di dati e questo metodo ha richiesto circa 300 ms contro circa 2,4 secondi per una query tradizionale. Con le query "or" annidate su set di dati più grandi, scommetto che le differenze saranno molto più drammatiche. – Hambone

Problemi correlati