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.
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. –
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
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. –