2012-03-28 33 views
9

thể trùng lặp:
PostgreSQL - max number of parameters in “IN” clause?Có hợp lý khi đưa 1000 id vào SELECT ... WHERE ... IN (...) truy vấn trên Postgres?

Tôi đang phát triển một API web để thực hiện truy vấn RESTful trên một tài nguyên mà các bản đồ này để thỏa mãn một bảng Postgres. Hầu hết các tham số lọc cũng ánh xạ độc đáo với các tham số trên truy vấn SQL. Tuy nhiên, một vài thông số lọc yêu cầu cuộc gọi đến chỉ mục tìm kiếm của tôi (trong trường hợp này là máy chủ Sphinx).

Điều đơn giản nhất cần làm là chạy tìm kiếm của tôi, thu thập các khóa chính từ kết quả tìm kiếm và đưa chúng vào một mệnh đề IN (...) trên truy vấn SQL. Tuy nhiên, kể từ khi tìm kiếm có thể trả về rất nhiều khóa chính, tôi tự hỏi liệu đây có phải là ý tưởng sáng chói hay không.

Tôi mong rằng hầu hết thời gian (nói, 90%), tìm kiếm của tôi sẽ trả lại kết quả theo thứ tự vài trăm. Có lẽ 10% thời gian, sẽ có hàng nghìn kết quả.

Đây có phải là cách tiếp cận hợp lý không? Có cách nào tốt hơn?

+1

Có thể, nhưng có lẽ không hoàn toàn. Câu hỏi đó yêu cầu kích thước tối đa của mệnh đề IN. Tôi hỏi, điều gì là hợp lý? Hay đó là một sự khác biệt quá chủ quan? Tôi sẽ thấy về việc lặp lại câu hỏi. –

+0

Bạn không thể làm: Ở đâu ... Trong (non_indexed_param1, non_indexed_param2, ...) Hoặc ... Trong (chọn ... Từ search_index ở đâu ...) Thay vì sử dụng hai truy vấn riêng biệt? – beny23

+0

Có lẽ tôi đã quá lỏng lẻo với ngôn ngữ của tôi. Bởi "chỉ mục tìm kiếm", tôi có nghĩa là máy chủ tìm kiếm, trong trường hợp này là Sphinx. Tôi đã làm rõ điều này trong câu hỏi. –

Trả lời

14

tôi rất ủng hộ cách tiếp cận thực nghiệm để trả lời câu hỏi hiệu suất. @Catcall đã thực hiện một khởi đầu tốt đẹp, nhưng có kích thước thử nghiệm của mình nhỏ hơn nhiều so với nhiều cơ sở dữ liệu thực sự. 300000 hàng nguyên duy nhất của nó dễ dàng phù hợp với bộ nhớ, vì vậy không có IO xảy ra; ngoài ra anh không chia sẻ những con số thực tế.

Tôi đã tạo một thử nghiệm tương tự, nhưng kích thước dữ liệu mẫu lớn gấp 7 lần bộ nhớ có sẵn trên máy chủ của tôi (bộ dữ liệu 7GB trên máy ảo CPU 1GB không có framtional, hệ thống tệp được gắn NFS). Có 30.000.000 hàng bao gồm một cột mốc được lập chỉ mục duy nhất và một chuỗi độ dài ngẫu nhiên trong khoảng từ 0 đến 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; 

Điều gì sau đây là giải thích phân tích thời gian chạy cho IN chọn trong số 10, 100, 1.000, 10.000 và 100.000 số nguyên ngẫu nhiên trong miền khóa. mỗi truy vấn ở dạng dưới đây, với $ 1 được thay thế bằng số đếm.

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

Tóm tắt Times

  • ct, ms tot, ms/hàng
  • 10, 84 tuổi, 8,4
  • 100, 1185, 11,8
  • 1.000, 12.407, 12,4
  • 10.000, 109747, 11.0
  • 100.000, 1.016.842, 10,1

Lưu ý kế hoạch giữ nguyên cho mỗi TRÊN bộ cardinality - xây dựng một tổng hash của các số nguyên ngẫu nhiên, sau đó vòng lặp và và làm một tra cứu được lập chỉ mục duy nhất cho mỗi giá trị. Thời gian tìm nạp là gần tuyến tính với cardinality của bộ IN, trong khoảng 8-12 ms/hàng. Một hệ thống lưu trữ nhanh hơn có thể cải thiện đáng kể thời gian này một cách đáng kể, nhưng thử nghiệm cho thấy rằng Pg xử lý các tập rất lớn trong mệnh đề IN với sự tự do - ít nhất là từ góc độ tốc độ thực thi. Lưu ý nếu bạn cung cấp danh sách thông qua tham số bind hoặc nội suy chữ của câu lệnh sql, bạn sẽ phải trả thêm phí trên mạng truyền truy vấn tới máy chủ và tăng số lần phân tích cú pháp, mặc dù tôi nghi ngờ chúng sẽ không đáng kể so với tot thời gian thoát truy vấn.

# 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 

Theo yêu cầu của @Catcall, tôi chạy các truy vấn tương tự bằng CTE và bảng tạm thời. Cả hai phương pháp đều có các kế hoạch quét chỉ mục vòng lặp lồng nhau tương đối đơn giản và chạy trong thời gian có thể so sánh (mặc dù hơi chậm hơn) như các truy vấn IN nội dòng.

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

Các truy vấn bảng temp chạy rất nhanh nếu chạy lại, nhưng đó là bởi vì các thiết lập giá trị id là hằng số, do đó dữ liệu mục tiêu là tươi trong bộ nhớ cache và Thạc làm không thực IO để thực hiện lần thứ hai.

+0

Bạn cũng đã thử nghiệm những người chống lại một tham gia trên một bảng tạm thời và tham gia vào một biểu thức bảng chung? –

+0

@Catcall thêm chạy với CTE và bảng tạm thời trong câu trả lời – dbenhur

4

Thử nghiệm hơi ngây thơ của tôi cho thấy rằng việc sử dụng IN (...) ít nhất là một đơn vị cường độ nhanh hơn cả tham gia trên bảng tạm thời và tham gia vào biểu thức bảng chung. (Thành thật mà nói, điều đó làm tôi ngạc nhiên.) Tôi đã thử nghiệm 3000 giá trị số nguyên từ một bảng 300000 hàng.

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

Thú vị! Cảm ơn bạn đã thử nghiệm điều đó. –

3

Để trả lời bài đăng @Catcall. Tôi không thể cưỡng lại để kiểm tra nó. Thật là kinh ngạc!!! Thay vì phản trực giác. kế hoạch thực hiện cũng tương tự (cả các truy vấn bằng cách sử dụng chỉ số tiềm ẩn) SELECT ... IN ...: enter image description hereSELECT ... 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; 
Các vấn đề liên quan