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