2012-03-28 15 views
9

Olası Çoğalt:
PostgreSQL - max number of parameters in “IN” clause?Postgre'lerde SELECT ... WHERE ... IN (...) sorgusuna 1000 kimliğin yerleştirilmesi mantıklı mı?

Bir Postgres tabloya güzel eşleştiren bir kaynakta RESTful sorguları gerçekleştirmek için bir web API geliştiriyorum

. Filtreleme parametrelerinin çoğu, SQL sorgusundaki parametrelere de iyi bir şekilde eşleşir. Bununla birlikte, birkaç filtreleme parametresi, arama dizinime bir çağrı gerektirir (bu durumda, bir Sphinx sunucusu).

Yapılması gereken en basit şey, aramamı çalıştırmak, birincil anahtarları arama sonuçlarından toplamak ve SQL sorgusunda IN (...) maddesine yerleştirmektir. Ancak, arama çok fazla birincil anahtar getirebileceğinden, bu kadar parlak bir fikir olup olmadığını merak ediyorum.

Çoğu zaman (% 90 gibi) aramalarımın sonuçlarının birkaç yüz mertebesinde geri dönmesini bekliyorum. Belki de zamanın% 10'u, birkaç bin sonucun sırası olacak.

Bu makul bir yaklaşım mıdır? Daha iyi bir yolu var mı?

+1

Belki, ama belki de tam olarak değil. Bu soru, IN yan tümcesinin maksimum boyutunu soruyor. Ben soruyorum, makul olan nedir? Yoksa bu çok öznel bir ayrım mı? Soruyu yeniden yazmayı göreceğim. –

+0

Yapamazsınız: Nerede ... In (non_indexed_param1, non_indexed_param2, ...) Veya ... In (select ... search_index Burada ...) İki ayrı sorgu kullanmak yerine? – beny23

+0

Belki de dilimle çok gevşek kaldım. "Arama dizini" ile arama sunucusunu, yani bu durumda Sfenks'i kastediyorum. Bunu açıklığa kavuşturdum. –

cevap

14

Yürütme planları benzer (örtük dizini kullanarak hem sorgular) SELECT ... IN ... bulunmaktadır. @Catcall iyi bir başlangıç ​​yaptı, ancak deneyini birçok gerçek veri tabanından çok daha küçüktü. 300000 tek tamsayı satırları belleğe kolayca sığar, dolayısıyla IO oluşmaz; ayrıca gerçek sayıları paylaşmadı.

Benzer bir denemeyi oluşturdum, ancak örnek verisi, ana bilgisayarımdaki kullanılabilir bellek kadar 7 kat daha büyük olacak şekilde boyutlandırdı (1 GB'lık 1-işlemcili CPU VM'sindeki 7 GB veri kümesi, NFS'ye bağlı dosya sistemi). Tek bir dizinlenmiş bigint ve 0 ile 400 bayt arasında rastgele bir uzunluk dizesinden oluşan 30.000.000 satır vardır. aşağıdaki Ne

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; 

olan tuşlar etki 10, 100, 1000, 10000 ve 100000 rastgele tamsayılar setleri seçkin bir IN için sürelerini analiz açıklar. Her sorgu aşağıdaki formdadır, $ 1 set sayımları ile değiştirilir.

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

Özeti Kez

  • ct, tot MS, MS/satır
  • 10, 84, 8.4
  • 100, 1185,
  • 1.000 11.8, 12407, 12.4
  • 10,000, 109747, 11.0
  • 100.000, 1.016.842, 10,1

Not planı seti kardinalitesi İÇİNDE her biri için aynı kalır - Rastgele bir tamsayı karma agrega, daha sonra döngü ve inşa etmek ve her değer için tek endeksli arama yapmak. Alma süresi, 8-12 ms/satır aralığında IN kümesinin asallığı ile lineer yakındır. Daha hızlı bir depolama sistemi, bu zamanları önemli ölçüde geliştirebilir, ancak deney, Pg'nin IN yan tümcesinde çok büyük kümeleri aplomb ile gerçekleştirdiğini gösterir - en azından yürütme hızı açısından. Listeyi bind-parametresi veya sql ifadesinin değişmez enterpolasyonu ile sağladığınız takdirde, sorguyu sunucuya iletmek için ek ek yüke maruz kalırsınız ve ayrıştırma sürelerini artırırsınız, ancak IO'ya kıyasla önemsiz olduğundan şüphelenirim. sorguyu belirleme zamanı.

# 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 

@Catcall 'un isteği üzerine CTE ve geçici tablo kullanarak benzer sorgulamalar yaptım. Her iki yaklaşım da karşılaştırmalı olarak basit yuva döngü indeksi tarama planlarına sahipti ve inline IN sorguları ile karşılaştırılabilir (ancak biraz daha yavaş) zamanlarda çalıştı. Tekrar çalıştırırsanız

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

geçici tablo sorguları çok daha hızlı koştu, ama id değeri seti sabittir, bu nedenle hedef veri önbellekte taze ve Pg ikinci kez çalıştırmak için hiçbir gerçek IO bunun nedeni de.

+0

Ayrıca, geçici tablodaki bir birleştirmeye ve ortak tablo ifadesinde bir birleştirmeye karşı test ettiniz mi? –

+0

@Catcall, CTE ve geçici tablo ile yanıt olarak çalışır – dbenhur

4

Bazı naif testlerim, IN (...) kullanmanın en azından bir geçici tablodaki birleşimden ve ortak bir tablo ifadesinde bir birleştirmeden daha büyük bir hız sırasının olduğunu gösteriyor. (Açıkçası beni şaşırttı.) 300000 satırlık bir tablodan 3000 tamsayı değerini test ettim. Buna karşılık

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

İlginç! Bunu test ettiğin için teşekkürler. –

3

yazı @Catcall için. Bunu iki kere test edemedim. Bu harika! Tersine karşı sezgisel. enter image description here ve SELECT ... JOIN ...: şiddetle performans sorulara cevap deneysel yaklaşımı tercih 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; 
İlgili konular