2011-03-18 31 views
7

Dưới đây là một ví dụ đơn giản cho vấn đề của tôi:chậm THAM GIA truy vấn với OR trong ĐÂU tuyên bố

CREATE TABLE test1 (id SERIAL, key TEXT UNIQUE, value TEXT); 
CREATE TABLE test2 (id SERIAL, key TEXT UNIQUE, value TEXT); 

INSERT INTO test1 (key, value) 
SELECT i::TEXT, 'ABC' || i::TEXT 
FROM generate_series(0, 1000000) AS i; 

INSERT INTO test2 (key, value) 
SELECT i::TEXT, 'ABC' || (i+1000)::TEXT 
FROM generate_series(0, 600000) AS i; 

INSERT INTO test2 (key, value) 
SELECT i::TEXT, 'ABC' || (i+1000)::TEXT 
FROM generate_series(1000000, 1200000) AS i; 

CREATE INDEX test1_key ON test1 (key); 
CREATE INDEX test1_value ON test1 (value); 
CREATE INDEX test2_key ON test2 (key); 
CREATE INDEX test2_value ON test2 (value); 

VACUUM FULL VERBOSE ANALYZE test1; 
VACUUM FULL VERBOSE ANALYZE test2; 

Đó là câu hỏi tôi hiện đang sử dụng, nhưng mà phải mất hơn 6 giây.

EXPLAIN ANALYZE 
SELECT test1.key AS key1, test1.value AS value1, 
     test2.key AS key2, test2.value AS value2 
FROM test1 
LEFT OUTER JOIN test2 ON (test1.key = test2.key) 
WHERE test1.value = 'ABC1234' OR test2.value = 'ABC1234'; 

key1 | value1 | key2 | value2 
------+---------+------+--------- 
234 | ABC234 | 234 | ABC1234 
1234 | ABC1234 | 1234 | ABC2234 
(2 rows) 

                 QUERY PLAN 
---------------------------------------------------------------------------------------------------------------------------- 
Hash Left Join (cost=27344.05..79728.10 rows=2 width=32) (actual time=5428.635..6097.098 rows=2 loops=1) 
    Hash Cond: (test1.key = test2.key) 
    Filter: ((test1.value = 'ABC1234'::text) OR (test2.value = 'ABC1234'::text)) 
    -> Seq Scan on test1 (cost=0.00..16321.01 rows=1000001 width=15) (actual time=0.009..1057.315 rows=1000001 loops=1) 
    -> Hash (cost=13047.02..13047.02 rows=800002 width=17) (actual time=2231.964..2231.964 rows=800002 loops=1) 
     Buckets: 65536 Batches: 2 Memory Usage: 14551kB 
     -> Seq Scan on test2 (cost=0.00..13047.02 rows=800002 width=17) (actual time=0.010..980.232 rows=800002 loops=1) 
Total runtime: 6109.042 ms 
(8 rows) 

Trong cả hai bảng chỉ rất ít bộ dữ liệu sẽ đáp ứng các yêu cầu, nhưng có vẻ như thực tế không được quan sát. Tôi thay vì có thể sử dụng một truy vấn như thế:

EXPLAIN ANALYZE 
SELECT coalesce(test1.key, test3.key1) AS key1, coalesce(test1.value, test3.value1) AS value1, 
     coalesce(test2.key, test3.key2) AS key2, coalesce(test2.value, test3.value2) AS value2 
FROM (SELECT test1.key AS key1, test1.value AS value1, 
      test2.key AS key2, test2.value AS value2 
     FROM (SELECT key, value FROM test1 WHERE value = 'ABC1234') AS test1 
     FULL JOIN (SELECT key, value FROM test2 WHERE value = 'ABC1234') AS test2 
     ON (test1.key = test2.key)) AS test3 
LEFT OUTER JOIN test1 ON (test1.key = test3.key2) 
LEFT OUTER JOIN test2 ON (test2.key = test3.key1) 
WHERE test1.key IS NOT NULL; 

key1 | value1 | key2 | value2 
------+---------+------+--------- 
1234 | ABC1234 | 1234 | ABC2234 
234 | ABC234 | 234 | ABC1234 
(2 rows) 

                   QUERY PLAN 
------------------------------------------------------------------------------------------------------------------------------------------ 
Nested Loop Left Join (cost=0.00..33.56 rows=1 width=64) (actual time=0.075..0.083 rows=1 loops=1) 
    -> Nested Loop (cost=0.00..25.19 rows=1 width=47) (actual time=0.066..0.072 rows=1 loops=1) 
     -> Nested Loop Left Join (cost=0.00..16.80 rows=1 width=32) (actual time=0.051..0.054 rows=1 loops=1) 
       -> Index Scan using test2_value_key on test2 (cost=0.00..8.41 rows=1 width=17) (actual time=0.026..0.027 rows=1 loops=1) 
        Index Cond: (value = 'ABC1234'::text) 
       -> Index Scan using test1_key on test1 (cost=0.00..8.38 rows=1 width=15) (actual time=0.020..0.020 rows=0 loops=1) 
        Index Cond: (public.test1.key = public.test2.key) 
        Filter: (public.test1.value = 'ABC1234'::text) 
     -> Index Scan using test1_key on test1 (cost=0.00..8.38 rows=1 width=15) (actual time=0.011..0.013 rows=1 loops=1) 
       Index Cond: ((public.test1.key IS NOT NULL) AND (public.test1.key = public.test2.key)) 
    -> Index Scan using test2_key on test2 (cost=0.00..8.36 rows=1 width=17) (actual time=0.001..0.001 rows=0 loops=1) 
     Index Cond: (public.test2.key = public.test1.key) 
Total runtime: 0.139 ms 

Các truy vấn sau đây là đơn giản, nhưng vẫn còn quá chậm:

EXPLAIN ANALYZE 
SELECT test1.key AS key1, test1.value AS value1, 
     test2.key AS key2, test2.value AS value2 
FROM test1 
LEFT OUTER JOIN test2 ON (test1.key = test2.key) 
WHERE test1.value = 'ABC1234' 
    OR EXISTS (SELECT 1 FROM test2 t WHERE t.key = test1.key AND t.value = 'ABC1234'); 

key1 | value1 | key2 | value2 
------+---------+------+--------- 
1234 | ABC1234 | 1234 | ABC2234 
234 | ABC234 | 234 | ABC1234 
(2 rows) 

                   QUERY PLAN 
---------------------------------------------------------------------------------------------------------------------------------------- 
Merge Left Join (cost=0.00..8446826.32 rows=500001 width=32) (actual time=615.706..1651.370 rows=2 loops=1) 
    Merge Cond: (test1.key = test2.key) 
    -> Index Scan using test1_key on test1 (cost=0.00..8398983.25 rows=500001 width=15) (actual time=28.449..734.567 rows=2 loops=1) 
     Filter: ((value = 'ABC1234'::text) OR (alternatives: SubPlan 1 or hashed SubPlan 2)) 
     SubPlan 1 
      -> Index Scan using test2_key on test2 t (cost=0.00..8.36 rows=1 width=0) (never executed) 
       Index Cond: (key = $0) 
       Filter: (value = 'ABC1234'::text) 
     SubPlan 2 
      -> Index Scan using test2_value on test2 t (cost=0.00..8.37 rows=1 width=7) (actual time=0.376..0.380 rows=1 loops=1) 
       Index Cond: (value = 'ABC1234'::text) 
    -> Index Scan using test2_key on test2 (cost=0.00..39593.05 rows=800002 width=17) (actual time=0.019..498.456 rows=348894 loops=1) 
Total runtime: 1651.453 ms 
(13 rows) 


Vì vậy, câu hỏi của tôi là: Có một truy vấn đơn giản mà sẽ dẫn đến một kế hoạch thực hiện nhanh tương tự như truy vấn thứ hai hoặc có thể là chỉ mục hoặc một số loại gợi ý cho trình lập kế hoạch.

(Tôi biết ví dụ rằng nó sẽ hợp lý để chỉ có một bảng với cả hai giá trị trong đó. Nhưng trong thực tế các bảng là phức tạp hơn và các chương trình bảng không thể thay đổi điều đó một cách dễ dàng.)


PostgreSQL Version: 9.0.3 
shared_buffers = 64MB 
effective_cache_size = 32MB 
work_mem = 16MB 
maintenance_work_mem = 32MB 
temp_buffers = 8MB 
wal_buffers= 1MB 


EDIT: Theo đề nghị từ Kipotlov đây là phiên bản UNION. Tại sao truy vấn OR bình thường không chọn một kế hoạch tốt như vậy?

EXPLAIN ANALYZE 
SELECT test1.key AS key1, test1.value AS value1, 
     test2.key AS key2, test2.value AS value2 
FROM test1 
LEFT OUTER JOIN test2 ON (test1.key = test2.key) 
WHERE test1.value = 'ABC1234' 
UNION 
SELECT test1.key AS key1, test1.value AS value1, 
     test2.key AS key2, test2.value AS value2 
FROM test1 
LEFT OUTER JOIN test2 ON (test1.key = test2.key) 
WHERE test2.value = 'ABC1234'; 

key1 | value1 | key2 | value2 
------+---------+------+--------- 
1234 | ABC1234 | 1234 | ABC2234 
234 | ABC234 | 234 | ABC1234 
(2 rows) 

                    QUERY PLAN 
------------------------------------------------------------------------------------------------------------------------------------------------ 
Unique (cost=33.64..33.66 rows=2 width=32) (actual time=0.114..0.119 rows=2 loops=1) 
    -> Sort (cost=33.64..33.64 rows=2 width=32) (actual time=0.111..0.113 rows=2 loops=1) 
     Sort Key: public.test1.key, public.test1.value, public.test2.key, public.test2.value 
     Sort Method: quicksort Memory: 17kB 
     -> Append (cost=0.00..33.63 rows=2 width=32) (actual time=0.046..0.097 rows=2 loops=1) 
       -> Nested Loop Left Join (cost=0.00..16.81 rows=1 width=32) (actual time=0.044..0.050 rows=1 loops=1) 
        -> Index Scan using test1_value_key on test1 (cost=0.00..8.44 rows=1 width=15) (actual time=0.023..0.024 rows=1 loops=1) 
          Index Cond: (value = 'ABC1234'::text) 
        -> Index Scan using test2_key on test2 (cost=0.00..8.36 rows=1 width=17) (actual time=0.014..0.016 rows=1 loops=1) 
          Index Cond: (public.test1.key = public.test2.key) 
       -> Nested Loop (cost=0.00..16.80 rows=1 width=32) (actual time=0.036..0.041 rows=1 loops=1) 
        -> Index Scan using test2_value_key on test2 (cost=0.00..8.41 rows=1 width=17) (actual time=0.019..0.020 rows=1 loops=1) 
          Index Cond: (value = 'ABC1234'::text) 
        -> Index Scan using test1_key on test1 (cost=0.00..8.38 rows=1 width=15) (actual time=0.013..0.015 rows=1 loops=1) 
          Index Cond: (public.test1.key = public.test2.key) 
Total runtime: 0.173 ms 
(16 rows) 
+4

Bạn có cố sử dụng 2 truy vấn và 'UNION' không? Truy vấn đầu tiên với mệnh đề where đầu tiên của bạn (test1.value) và thứ hai với mệnh đề where thứ hai (test2.value). Tôi không biết nếu nó sẽ nhanh hơn hay không .. – Kipotlov

+2

@Kipotlov Tôi đã thêm truy vấn UNION. Đó là nhanh như truy vấn thứ hai, nhưng tôi không chắc chắn nếu tôi có thể áp dụng nó vào vấn đề thực sự của tôi. Bất kỳ ý tưởng nào tại sao truy vấn OR bình thường thích quét tuần tự? –

Trả lời

6

Trước hết, cảm ơn câu hỏi rất chi tiết. Thật hiếm khi tìm thấy những người đã nghiên cứu vấn đề của họ vào chi tiết như vậy trước khi yêu cầu.

Tôi đã suy nghĩ về điều này và vấn đề dường như là PostgreSQL muốn tham gia tất cả các hàng, bởi vì mỗi hàng không phù hợp từ test1 có thể được kết hợp trong test2 - và ngược lại.

Giải pháp là buộc nhà lập kế hoạch thực hiện truy vấn theo hai bước. Một cách tiếp cận là truy vấn UNION lớn mà bạn đã thử - để buộc nó xem xét từng biểu thức trong một truy vấn riêng biệt.

cách tiếp cận khác là buộc các nhà quy hoạch để tìm phù hợp với phím đầu tiên, sau đó thực hiện tham gia, vì vậy không thể có sự nhập nhằng:

EXPLAIN ANALYZE 
SELECT test1.key AS key1, test1.value AS value1, 
     test2.key AS key2, test2.value AS value2 
FROM (
    SELECT key FROM test1 WHERE value='ABC1234' 
    UNION SELECT key FROM test2 WHERE value='ABC1234' 
) AS matching_keys 
INNER JOIN test1 USING (key) 
LEFT OUTER JOIN test2 USING (key); 

Nested Loop Left Join (cost=16.84..34.44 rows=2 width=32) (actual time=0.211..0.280 rows=2 loops=1) 
    -> Nested Loop (cost=16.84..33.65 rows=2 width=15) (actual time=0.175..0.212 rows=2 loops=1) 
     -> Unique (cost=16.84..16.85 rows=2 width=6) (actual time=0.132..0.136 rows=2 loops=1) 
       -> Sort (cost=16.84..16.85 rows=2 width=6) (actual time=0.131..0.132 rows=2 loops=1) 
        Sort Key: public.test1.key 
        Sort Method: quicksort Memory: 25kB 
        -> Append (cost=0.00..16.83 rows=2 width=6) (actual time=0.058..0.110 rows=2 loops=1) 
          -> Index Scan using test1_value on test1 (cost=0.00..8.42 rows=1 width=6) (actual time=0.056..0.058 rows=1 loops=1) 
           Index Cond: (value = 'ABC1234'::text) 
          -> Index Scan using test2_value on test2 (cost=0.00..8.39 rows=1 width=7) (actual time=0.046..0.047 rows=1 loops=1) 
           Index Cond: (value = 'ABC1234'::text) 
     -> Index Scan using test1_key on test1 (cost=0.00..8.38 rows=1 width=15) (actual time=0.032..0.033 rows=1 loops=2) 
       Index Cond: (key = public.test1.key) 
    -> Index Scan using test2_key on test2 (cost=0.00..0.38 rows=1 width=17) (actual time=0.028..0.029 rows=1 loops=2) 
     Index Cond: (public.test1.key = key) 
Total runtime: 0.390 ms 
(16 rows) 

Một lần nữa, UNION phục vụ vai trò của OR. Rất tiếc, phương pháp này vẫn hoạt động kém đối với các truy vấn như value>'ABC1234'.Bạn có thể cải thiện nó bằng cách chạm lên work_mem. Tôi đang thua lỗ ở đây.


Đối với câu hỏi cuối cùng của bạn:

Tại sao bình thường OR truy vấn không chọn một kế hoạch tốt như vậy?

Do trình hoạch định PostgreSQL hiện thiếu khả năng tách biểu thức OR thành các truy vấn UNION riêng biệt. Có một vài cảnh báo làm cho nó khó hơn nó có vẻ.

Công cụ lập kế hoạch PostgreSQL khá phức tạp, nhưng cho đến nay nó vẫn chưa được ưu tiên lớn tận dụng tối ưu hóa đã có thể bằng cách viết lại SQL theo cách thủ công.

+2

Tôi không chắc liệu tối ưu hóa đó luôn có thể bằng cách viết lại thủ công hay không. Ví dụ nếu '?' Là giá trị do người dùng cung cấp và điều kiện là 'giá trị

+1

Tôi đã cập nhật phản hồi của mình bằng cách tiếp cận khác. Thật không may, không tốt hơn nhiều. – intgr

1

Tôi không biết cách nào tốt hơn hoặc nhanh hơn.

Nhưng điều đầu tiên tôi chú ý là: Bạn có hai bảng với mỗi cột khóa (UNIQUE) trong mỗi cột. Sau đó, bạn nhận dữ liệu từ hai bảng cho cùng một khóa.

Điểm của tôi ở đây là tại sao bạn không tham gia hai bảng ngay từ đầu để bạn chỉ phải nhận từ một bảng?

+1

Hai bảng thực của tôi có nhiều cột hơn và không chỉ được nối bởi một cột đơn. Tôi chỉ đơn giản hóa trường hợp của tôi trong khi vẫn nhận được kết quả tương tự. Tôi không thể hợp nhất hai bảng thực của tôi thành một, dễ dàng như vậy. –

Các vấn đề liên quan