Truy vấn PostgreSQL rất chậm khi thêm truy vấn con


9

Tôi có một truy vấn tương đối đơn giản trên một bảng có 1,5M hàng:

SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;

EXPLAIN ANALYZE đầu ra:

Limit  (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1)
  ->  Bitmap Heap Scan on publication  (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1)
        Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321))
        ->  BitmapOr  (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1)
              ->  Bitmap Index Scan on publication_pkey  (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1)
                    Index Cond: (mtid = 9762715)
              ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1)
                    Index Cond: (last_modifier = 21321)
Total runtime: 1.027 ms

Cho đến nay rất tốt, nhanh chóng và sử dụng các chỉ mục có sẵn.
Bây giờ, nếu tôi sửa đổi một truy vấn chỉ một chút, kết quả sẽ là:

SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;

Đầu EXPLAIN ANALYZEra là:

Limit  (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1)
  ->  Seq Scan on publication  (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1)
        Filter: ((hashed SubPlan 1) OR (last_modifier = 21321))
        SubPlan 1
          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
Total runtime: 2841.442 ms

Không quá nhanh và sử dụng quét seq ...

Tất nhiên, truy vấn ban đầu được chạy bởi ứng dụng phức tạp hơn một chút và thậm chí chậm hơn, và tất nhiên bản gốc được tạo ra bởi chế độ ngủ đông thì không (SELECT 9762715), nhưng sự chậm chạp thậm chí còn có cho điều đó (SELECT 9762715)! Truy vấn được tạo bởi hibernate, do đó, việc thay đổi chúng là một thách thức và một số tính năng không khả dụng (ví dụ: UNIONkhông khả dụng, sẽ nhanh).

Những câu hỏi

  1. Tại sao chỉ mục không thể được sử dụng trong trường hợp thứ hai? Làm thế nào chúng có thể được sử dụng?
  2. Tôi có thể cải thiện hiệu suất truy vấn theo một cách khác không?

Suy nghĩ thêm

Có vẻ như chúng ta có thể sử dụng trường hợp đầu tiên bằng cách thực hiện thủ công CHỌN và sau đó đưa danh sách kết quả vào truy vấn. Ngay cả với 5000 số trong danh sách IN (), nó nhanh hơn bốn lần so với giải pháp thứ hai. Tuy nhiên, nó chỉ có vẻ SAU (cũng vậy, nó có thể nhanh hơn 100 lần :)). Hoàn toàn không thể hiểu được tại sao trình hoạch định truy vấn sử dụng một phương pháp hoàn toàn khác cho hai truy vấn này, vì vậy tôi muốn tìm một giải pháp đẹp hơn cho vấn đề này.


Bạn có thể bằng cách nào đó viết lại mã của bạn để ngủ đông tạo ra một JOINthay vì IN ()? Ngoài ra, đã publicationđược phân tích gần đây?
dezso

Có, tôi đã làm cả VACUUM ANALYZE và VACUUM FULL. Không có thay đổi trong hiệu suất. Lần thứ hai, AFAIR chúng tôi đã thử điều đó và nó không ảnh hưởng đáng kể đến hiệu năng truy vấn.
P.Péter

1
Nếu Hibernate không tạo được một truy vấn thích hợp, tại sao bạn không sử dụng SQL thô? Điều đó giống như nhấn mạnh vào Google dịch trong khi bạn đã biết rõ hơn cách diễn đạt bằng tiếng Anh. Đối với câu hỏi của bạn: nó thực sự phụ thuộc vào truy vấn thực tế ẩn đằng sau (SELECT 9762715).
Erwin Brandstetter

Như tôi đã đề cập dưới đây, nó chậm ngay cả khi truy vấn bên trong (SELECT 9762715) . Đối với câu hỏi ngủ đông: nó có thể được thực hiện, nhưng yêu cầu viết lại mã nghiêm túc, vì chúng tôi có các tiêu chí ngủ đông do người dùng định nghĩa được dịch nhanh chóng. Vì vậy, về cơ bản, chúng tôi sẽ sửa đổi chế độ ngủ đông là một công việc khổng lồ với rất nhiều tác dụng phụ có thể xảy ra.
P.Péter

Câu trả lời:


6

Đồng nghiệp của tôi đã tìm ra cách thay đổi truy vấn để nó cần viết lại đơn giản và thực hiện những gì cần làm, tức là thực hiện chọn phụ trong một bước, sau đó thực hiện các thao tác tiếp theo về kết quả:

SELECT mtid FROM publication 
WHERE 
  mtid = ANY( (SELECT ARRAY(SELECT 9762715))::bigint[] )
  OR last_modifier=21321
LIMIT 5000;

Giải thích phân tích bây giờ là:

 Limit  (cost=92.58..9442.38 rows=2478 width=8) (actual time=0.071..0.074 rows=1 loops=1)
   InitPlan 2 (returns $1)
     ->  Result  (cost=0.01..0.02 rows=1 width=0) (actual time=0.010..0.011 rows=1 loops=1)
           InitPlan 1 (returns $0)
             ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.002 rows=1 loops=1)
   ->  Bitmap Heap Scan on publication  (cost=92.56..9442.36 rows=2478 width=8) (actual time=0.069..0.070 rows=1 loops=1)
         Recheck Cond: ((mtid = ANY (($1)::bigint[])) OR (last_modifier = 21321))
         Heap Blocks: exact=1
         ->  BitmapOr  (cost=92.56..92.56 rows=2478 width=0) (actual time=0.060..0.060 rows=0 loops=1)
               ->  Bitmap Index Scan on publication_pkey  (cost=0.00..44.38 rows=10 width=0) (actual time=0.046..0.046 rows=1 loops=1)
                     Index Cond: (mtid = ANY (($1)::bigint[]))
               ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..46.94 rows=2468 width=0) (actual time=0.011..0.011 rows=0 loops=1)
                     Index Cond: (last_modifier = 21321)
 Planning time: 0.704 ms
 Execution time: 0.153 ms

Dường như chúng ta có thể tạo một trình phân tích cú pháp đơn giản để tìm và viết lại tất cả các mục con theo cách này và thêm nó vào một cái móc ngủ đông để thao tác truy vấn gốc.


Nghe vui đó. Không phải là dễ dàng hơn để loại bỏ tất cả các SELECTs, như bạn có trong truy vấn đầu tiên của bạn trong câu hỏi?
dezso

Tất nhiên, tôi có thể thực hiện một cách tiếp cận hai bước: thực hiện một SELECTcách riêng biệt, và sau đó thực hiện lựa chọn bên ngoài với một danh sách tĩnh sau IN. Tuy nhiên, điều đó chậm hơn đáng kể (5-10 lần nếu truy vấn con có nhiều hơn một vài kết quả), vì bạn có thêm các chuyến đi khứ hồi mạng cộng với bạn có định dạng postgres rất nhiều kết quả và sau đó java phân tích các kết quả đó (và sau đó thực hiện cùng một lần nữa ngược). Các giải pháp ở trên thực hiện cùng một ngữ nghĩa, trong khi để lại quá trình bên trong postgres. Nhìn chung, hiện tại đây có vẻ là cách nhanh nhất với sửa đổi nhỏ nhất trong trường hợp của chúng tôi.
P.Péter

Ah tôi thấy. Điều tôi không biết là bạn có thể nhận được nhiều ID cùng một lúc.
dezso

5

Cốt lõi của vấn đề trở nên rõ ràng ở đây:

Quét Seq trên xuất bản (chi phí = 0,01..349652.84 hàng = 744661 width = 8) (thời gian thực tế = 2735.888..2841.393 hàng = 1 vòng = 1)

Postgres ước tính trả về 744661 hàng trong khi trên thực tế, nó hóa ra là một hàng đơn. Nếu Postgres không biết rõ hơn những gì mong đợi từ truy vấn thì nó không thể lập kế hoạch tốt hơn. Chúng ta sẽ cần phải xem truy vấn thực tế ẩn đằng sau (SELECT 9762715)- và có lẽ cũng biết định nghĩa bảng, các ràng buộc, số lượng và phân phối dữ liệu. Rõ ràng, Postgres không thể dự đoán số lượng hàng sẽ được trả lại bởi nó. Có thể có cách viết lại truy vấn, tùy thuộc vào nó gì .

Nếu bạn biết rằng truy vấn con không bao giờ có thể trả về nhiều hơn nhàng, bạn chỉ có thể nói với Postgres bằng cách sử dụng:

SELECT mtid
FROM   publication
WHERE  mtid IN (SELECT ... LIMIT n) --  OR last_modifier=21321
LIMIT  5000;

Nếu n đủ nhỏ, Postgres sẽ chuyển sang quét chỉ mục (bitmap). Tuy nhiên , điều đó chỉ hoạt động cho trường hợp đơn giản. Dừng hoạt động khi thêm một ORđiều kiện: trình hoạch định truy vấn hiện không thể đối phó với điều đó.

Tôi hiếm khi sử dụng IN (SELECT ...)để bắt đầu với. Thông thường có một cách tốt hơn để thực hiện tương tự, thường là với một EXISTSnửa tham gia. Đôi khi với một ( LEFT) JOIN( LATERAL) ...

Cách giải quyết rõ ràng sẽ là sử dụng UNION, nhưng bạn loại trừ. Tôi không thể nói nhiều hơn mà không biết truy vấn con thực tế và các chi tiết liên quan khác.


2
Không có truy vấn ẩn đằng sau (SELECT 9762715) ! Nếu tôi chạy truy vấn chính xác mà bạn thấy ở trên. Tất nhiên, truy vấn ngủ đông ban đầu phức tạp hơn một chút, nhưng tôi (nghĩ rằng tôi) đã xác định được chính xác nơi trình hoạch định truy vấn đi lạc hướng, vì vậy tôi đã trình bày một phần của truy vấn đó. Tuy nhiên, các giải thích và truy vấn ở trên là nguyên văn ctrl-cv.
P.Péter

Đối với phần thứ hai, giới hạn bên trong không hoạt động: EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;cũng thực hiện quét tuần tự và cũng chạy trong khoảng 3 giây ...
P.Péter

@ P.Péter: Nó hoạt động với tôi trong thử nghiệm cục bộ của tôi với một truy vấn con thực tế trên Postgres 9.4. Nếu những gì bạn hiển thị là truy vấn thực sự của bạn, thì bạn đã có giải pháp của mình: Sử dụng truy vấn đầu tiên trong câu hỏi của bạn với hằng số thay vì truy vấn con.
Erwin Brandstetter

Vâng, tôi cũng đã thử một truy vấn con trên một bảng thử nghiệm mới : CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;. Và hiệu quả vẫn còn đó cho các truy vấn tương tự trên test: bất kỳ truy vấn con nào dẫn đến quét seq ... Tôi đã thử cả 9.1 và 9.4. Hiệu quả là như nhau.
P.Péter

1
@ P.Péter: Tôi đã chạy thử nghiệm một lần nữa và nhận ra tôi đã thử nghiệm mà không cần ORđiều kiện. Thủ thuật LIMITchỉ hoạt động cho trường hợp đơn giản hơn.
Erwin Brandstetter

1

Trả lời cho câu hỏi thứ hai: Có, bạn có thể thêm ĐẶT HÀNG THEO cho truy vấn con của mình, điều này sẽ có tác động tích cực. Nhưng đó là simillar cho giải pháp "EXISTS (subquery)" trong hiệu suất. Có một sự khác biệt đáng kể ngay cả với truy vấn con dẫn đến hai hàng.

SELECT mtid FROM publication
WHERE mtid IN (SELECT #column# ORDER BY #column#) OR last_modifier=21321
LIMIT 5000;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.