2012-03-28 9 views
9

Eventuali duplicati:
PostgreSQL - max number of parameters in “IN” clause?È ragionevole inserire 1000 ID in una SELECT ... WHERE ... IN (...) query su Postgres?

sto sviluppando un'API web di eseguire query RESTful su una risorsa che mappa bene a una tabella Postgres. La maggior parte dei parametri di filtraggio si adatta anche ai parametri della query SQL. Alcuni parametri di filtraggio, tuttavia, richiedono una chiamata al mio indice di ricerca (in questo caso, un server Sphinx).

La cosa più semplice da fare è eseguire la mia ricerca, raccogliere le chiavi primarie dai risultati della ricerca e inserirle in una clausola IN (...) nella query SQL. Tuttavia, poiché la ricerca potrebbe restituire molte chiavi primarie, mi chiedo se questa idea così brillante.

Mi aspetto che la maggior parte delle volte (ad esempio, il 90%), la mia ricerca restituirà risultati dell'ordine di poche centinaia. Forse il 10% delle volte, ci sarà dell'ordine di diverse migliaia di risultati.

È un approccio ragionevole? C'è un modo migliore?

+1

Forse, ma forse non del tutto. Questa domanda sta chiedendo la dimensione massima della clausola IN. Sto chiedendo, cosa è ragionevole? O è quella distinzione troppo soggettiva? Vedrò di riformulare la domanda. –

+0

Non si può fare: dove ... In (non_indexed_param1, non_indexed_param2, ...) Oppure ... In (selezionare ... Da search_index dove ...) Piuttosto che usare due query separate? – beny23

+0

Forse ero troppo sciolto con la mia lingua. Per "indice di ricerca" intendo il server di ricerca, in questo caso, Sfinge. Ho chiarito questo nella domanda. –

risposta

14

Io sono a favore fortemente l'approccio sperimentale per rispondere alle domande di prestazioni. @Catcall ha fatto un buon inizio, ma ha dimensionato il suo esperimento molto più piccolo di molti veri e propri database. Le sue 300000 righe intere singole si adattano facilmente alla memoria, quindi non si verifica alcun IO; inoltre non ha condiviso i numeri reali.

Ho composto un esperimento simile, ma ho ridimensionato i dati di esempio di circa 7 volte quanto la memoria disponibile sul mio host (set di dati da 7 GB su una CPU con CPU da 1 GB e sistema operativo montato su NFS). Esistono 30.000.000 di righe composte da un singolo bigint indicizzato e una stringa di lunghezza casuale compresa tra 0 e 400 byte.

create table t(id bigint primary key, stuff text); 
insert into t(id,stuff) select i, repeat('X',(random()*400)::integer) 
from generate_series(0,30000000) i; 
analyze t; 

Quello che segue sono spiegare analizzare i tempi di esecuzione per una selezionata IN di serie da 10, 100, 1.000, 10.000 e 100.000 interi casuali nel dominio chiavi. ogni query è nella seguente forma, con $ 1 sostituito dai conteggi impostati.

explain analyze 
select id from t 
where id in (
    select (random()*30000000)::integer from generate_series(0,$1) 
); 

Sommario tempi

  • ct, ms tot, ms/riga
  • 10, 84, 8.4
  • 100, 1185, 11.8
  • 1000, 12407, 12.4
  • 10.000, 109747, 11.0
  • 100.000, 1.016.842, 10,1

Nota il piano rimane la stessa per ogni IN set cardinalità - costruire un aggregato hash dei numeri interi casuali, poi loop e e fare una sola ricerca indicizzato per ogni valore. Il tempo di recupero è quasi lineare con la cardinalità del set IN, nell'intervallo 8-12 ms/riga. Un sistema di storage più veloce potrebbe senza dubbio migliorare notevolmente questi tempi, ma l'esperimento mostra che Pg gestisce serie molto grandi nella clausola IN con aplomb - almeno dal punto di vista della velocità di esecuzione. Nota se fornisci l'elenco tramite il parametro bind o l'interpolazione letterale dell'istruzione sql, dovrai sostenere un sovraccarico aggiuntivo sulla trasmissione di rete della query al server e maggiori tempi di analisi, sebbene sospetto che saranno trascurabili rispetto a quelli di IO tempo di uscire dalla query.

# fetch 10 
Nested Loop (cost=30.00..2341.27 rows=15002521 width=8) (actual time=0.110..84.494 rows=11 loops=1) 
    -> HashAggregate (cost=30.00..32.00 rows=200 width=4) (actual time=0.046..0.054 rows=11 loops=1) 
     -> Function Scan on generate_series (cost=0.00..17.50 rows=1000 width=0) (actual time=0.036..0.039 rows=11 loops=1) 
    -> Index Scan using t_pkey on t (cost=0.00..11.53 rows=1 width=8) (actual time=7.672..7.673 rows=1 loops=11) 
     Index Cond: (t.id = (((random() * 30000000::double precision))::integer)) 
Total runtime: 84.580 ms 


# fetch 100 
Nested Loop (cost=30.00..2341.27 rows=15002521 width=8) (actual time=12.405..1184.758 rows=101 loops=1) 
    -> HashAggregate (cost=30.00..32.00 rows=200 width=4) (actual time=0.095..0.210 rows=101 loops=1) 
     -> Function Scan on generate_series (cost=0.00..17.50 rows=1000 width=0) (actual time=0.046..0.067 rows=101 loops=1) 
    -> Index Scan using t_pkey on t (cost=0.00..11.53 rows=1 width=8) (actual time=11.723..11.725 rows=1 loops=101) 
     Index Cond: (t.id = (((random() * 30000000::double precision))::integer)) 
Total runtime: 1184.843 ms 

# fetch 1,000 
Nested Loop (cost=30.00..2341.27 rows=15002521 width=8) (actual time=14.403..12406.667 rows=1001 loops=1) 
    -> HashAggregate (cost=30.00..32.00 rows=200 width=4) (actual time=0.609..1.689 rows=1001 loops=1) 
     -> Function Scan on generate_series (cost=0.00..17.50 rows=1000 width=0) (actual time=0.128..0.332 rows=1001 loops=1) 
    -> Index Scan using t_pkey on t (cost=0.00..11.53 rows=1 width=8) (actual time=12.381..12.390 rows=1 loops=1001) 
     Index Cond: (t.id = (((random() * 30000000::double precision))::integer)) 
Total runtime: 12407.059 ms 

# fetch 10,000 
Nested Loop (cost=30.00..2341.27 rows=15002521 width=8) (actual time=21.884..109743.854 rows=9998 loops=1) 
    -> HashAggregate (cost=30.00..32.00 rows=200 width=4) (actual time=5.761..18.090 rows=9998 loops=1) 
     -> Function Scan on generate_series (cost=0.00..17.50 rows=1000 width=0) (actual time=1.004..3.087 rows=10001 loops=1) 
    -> Index Scan using t_pkey on t (cost=0.00..11.53 rows=1 width=8) (actual time=10.968..10.972 rows=1 loops=9998) 
     Index Cond: (t.id = (((random() * 30000000::double precision))::integer)) 
Total runtime: 109747.169 ms 

# fetch 100,000 
Nested Loop (cost=30.00..2341.27 rows=15002521 width=8) (actual time=110.244..1016781.944 rows=99816 loops=1) 
    -> HashAggregate (cost=30.00..32.00 rows=200 width=4) (actual time=110.169..253.947 rows=99816 loops=1) 
     -> Function Scan on generate_series (cost=0.00..17.50 rows=1000 width=0) (actual time=51.141..77.482 rows=100001 loops=1) 
    -> Index Scan using t_pkey on t (cost=0.00..11.53 rows=1 width=8) (actual time=10.176..10.181 rows=1 loops=99816) 
     Index Cond: (t.id = (((random() * 30000000::double precision))::integer)) 
Total runtime: 1016842.772 ms 

Su richiesta @Catcall s', mi sono imbattuto query simili che utilizzano CTE e tabella temporanea. Entrambi gli approcci avevano piani di scansione dell'indice di loop semplice e comparabili e funzionavano in termini comparabili (sebbene leggermente più lenti) rispetto alle query IN inline.

-- CTE 
EXPLAIN analyze 
with ids as (select (random()*30000000)::integer as val from generate_series(0,1000)) 
select id from t where id in (select ids.val from ids); 

Nested Loop (cost=40.00..2351.27 rows=15002521 width=8) (actual time=21.203..12878.329 rows=1001 loops=1) 
    CTE ids 
    -> Function Scan on generate_series (cost=0.00..17.50 rows=1000 width=0) (actual time=0.085..0.306 rows=1001 loops=1) 
    -> HashAggregate (cost=22.50..24.50 rows=200 width=4) (actual time=0.771..1.907 rows=1001 loops=1) 
     -> CTE Scan on ids (cost=0.00..20.00 rows=1000 width=4) (actual time=0.087..0.552 rows=1001 loops=1) 
    -> Index Scan using t_pkey on t (cost=0.00..11.53 rows=1 width=8) (actual time=12.859..12.861 rows=1 loops=1001) 
     Index Cond: (t.id = ids.val) 
Total runtime: 12878.812 ms 
(8 rows) 

-- Temp table 
create table temp_ids as select (random()*30000000)::bigint as val from generate_series(0,1000); 

explain analyze select id from t where t.id in (select val from temp_ids); 

Nested Loop (cost=17.51..11585.41 rows=1001 width=8) (actual time=7.062..15724.571 rows=1001 loops=1) 
    -> HashAggregate (cost=17.51..27.52 rows=1001 width=8) (actual time=0.268..1.356 rows=1001 loops=1) 
     -> Seq Scan on temp_ids (cost=0.00..15.01 rows=1001 width=8) (actual time=0.007..0.080 rows=1001 loops=1) 
    -> Index Scan using t_pkey on t (cost=0.00..11.53 rows=1 width=8) (actual time=15.703..15.705 rows=1 loops=1001) 
     Index Cond: (t.id = temp_ids.val) 
Total runtime: 15725.063 ms 

-- another way using join against temptable insteed of IN 
explain analyze select id from t join temp_ids on (t.id = temp_ids.val); 

Nested Loop (cost=0.00..24687.88 rows=2140 width=8) (actual time=22.594..16557.789 rows=1001 loops=1) 
    -> Seq Scan on temp_ids (cost=0.00..31.40 rows=2140 width=8) (actual time=0.014..0.872 rows=1001 loops=1) 
    -> Index Scan using t_pkey on t (cost=0.00..11.51 rows=1 width=8) (actual time=16.536..16.537 rows=1 loops=1001) 
     Index Cond: (t.id = temp_ids.val) 
Total runtime: 16558.331 ms 

Le query tabella temporanea correvano molto più veloce se eseguire di nuovo, ma questo è perché il valore impostato id è costante, quindi i dati di destinazione è fresco nella cache e Pg non fa vero e proprio IO per eseguire la seconda volta.

+0

Hai testato anche quelli contro un join su una tabella temporanea e un join su un'espressione di tabella comune? –

+0

@Catcall add viene eseguito con CTE e tabella temporanea in risposta – dbenhur

4

I miei test un po 'ingenui mostrano che l'utilizzo di IN (...) è almeno un ordine di grandezza più veloce di un join su una tabella temporanea e un join su un'espressione di tabella comune. (Francamente, questo mi ha sorpreso.) Ho testato 3000 valori interi da una tabella di 300000 righe.

create table integers (
    n integer primary key 
); 
insert into integers 
select generate_series(0, 300000); 

-- External ruby program generates 3000 random integers in the range of 0 to 299999. 
-- Used Emacs to massage the output into a SQL statement that looks like 

explain analyze 
select integers.n 
from integers where n in (
100109, 
100354 , 
100524 , 
... 
); 
+0

Interessante! Grazie per averlo testato. –

3

In risposta al post @Catcall. Non ho potuto resistere per raddoppiarlo. È stupefacente!!! Piuttosto contro-intuitivo. piani di esecuzione sono simili (entrambe le query utilizzando l'indice implicito) SELECT ... IN ...: enter image description here e SELECT ... JOIN ...: enter image description here

CREATE TABLE integers (
    n integer PRIMARY KEY 
); 
INSERT INTO integers 
SELECT generate_series(0, 300000); 

CREATE TABLE search ( n integer); 

-- Generate INSERTS and SELECT ... WHERE ... IN (...) 
SELECT 'SELECT integers.n 
FROM integers WHERE n IN (' || list || ');', 
' INSERT INTO search VALUES ' 
|| values ||'; ' FROM (
SELECT string_agg(n::text, ',') AS list, string_agg('('||n::text||')', ',') AS values FROM (
SELECT n FROM integers ORDER BY random() LIMIT 3000) AS elements) AS raw 


INSERT INTO search VALUES (9155),(189177),(18815),(13027),... ; 

EXPLAIN SELECT integers.n 
FROM integers WHERE n IN (9155,189177,18815,13027,...); 

EXPLAIN SELECT integers.n FROM integers JOIN search ON integers.n = search.n; 
Problemi correlati